Mattias Bodlund 4 days ago
parent
commit
a8ae566f0e
52 changed files with 658 additions and 1086 deletions
  1. BIN
      .DS_Store
  2. +2
    -0
      .gitignore
  3. +19
    -32
      Gemfile
  4. +356
    -136
      Gemfile.lock
  5. +12
    -12
      README DB.txt
  6. +3
    -3
      README.txt
  7. +0
    -42
      app/controllers/answers_controller.rb
  8. +1
    -4
      app/controllers/concerns/quiz_helper_methods.rb
  9. +6
    -9
      app/controllers/languages_controller.rb
  10. +0
    -42
      app/controllers/players_controller.rb
  11. +0
    -94
      app/controllers/questions_controller.rb
  12. +3
    -8
      app/controllers/site_controller.rb
  13. +0
    -2
      app/helpers/answers_helper.rb
  14. +0
    -2
      app/helpers/players_helper.rb
  15. +0
    -99
      app/helpers/questions_helper.rb
  16. +1
    -1
      app/javascript/controllers/select_controller.js
  17. +0
    -11
      app/models/answer.rb
  18. +0
    -28
      app/models/player.rb
  19. +0
    -15
      app/models/quiz_result.rb
  20. +1
    -14
      app/views/languages/index.html.erb
  21. +3
    -14
      app/views/layouts/application.html.erb
  22. +0
    -39
      app/views/players/new.html.erb
  23. +0
    -45
      app/views/questions/answer.html.erb
  24. +0
    -41
      app/views/questions/result.html.erb
  25. +0
    -41
      app/views/questions/shared_result.html.erb
  26. +0
    -57
      app/views/questions/show.html.erb
  27. +6
    -0
      bin/ci
  28. +1
    -1
      bin/rubocop
  29. +1
    -0
      bin/setup
  30. +5
    -0
      bin/thrust
  31. +5
    -4
      config/application.rb
  32. +1
    -1
      config/cable.yml
  33. +22
    -0
      config/ci.rb
  34. +6
    -6
      config/importmap.rb
  35. +4
    -0
      config/initializers/content_security_policy.rb
  36. +0
    -70
      config/initializers/new_framework_defaults_7_2.rb
  37. +0
    -30
      config/initializers/new_framework_defaults_8_0.rb
  38. +37
    -55
      config/nginx.conf
  39. +0
    -14
      db/migrate/20250429085114_create_players.rb
  40. +0
    -14
      db/migrate/20250429121602_create_answers.rb
  41. +0
    -6
      db/migrate/20250522111116_add_score_to_players.rb
  42. +0
    -15
      db/migrate/20250527114853_create_quiz_results.rb
  43. +46
    -46
      db/schema.rb
  44. +1
    -1
      db/seeds.rb
  45. +81
    -0
      lib/tasks/quiz_stats.rake
  46. +1
    -1
      mise.toml
  47. BIN
      public/icon.png
  48. +3
    -0
      public/icon.svg
  49. +2
    -2
      vendor/javascript/@rails--activestorage.js
  50. +2
    -2
      vendor/javascript/@rails--request.js.js
  51. +24
    -24
      vendor/javascript/sortablejs.js
  52. +3
    -3
      vendor/javascript/trix.js

BIN
.DS_Store View File


+ 2
- 0
.gitignore View File

@ -22,6 +22,8 @@
!/tmp/pids/ !/tmp/pids/
!/tmp/pids/.keep !/tmp/pids/.keep
.rubocop.yml
# Ignore storage (uploaded files in development and any SQLite databases). # Ignore storage (uploaded files in development and any SQLite databases).
/storage/* /storage/*
!/storage/.keep !/storage/.keep


+ 19
- 32
Gemfile View File

@ -1,49 +1,50 @@
source "https://rubygems.org" source "https://rubygems.org"
ruby "3.4.1"
ruby "3.4.9"
gem 'dotenv-rails'
gem "dotenv-rails"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 8.0.2"
gem "rails", "~> 8.1.2"
gem "openssl" gem "openssl"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
# gem "sprockets-rails" # gem "sprockets-rails"
gem 'turbo-rails'
gem 'stimulus-rails'
gem "turbo-rails"
gem "stimulus-rails"
gem "propshaft" gem "propshaft"
gem "importmap-rails" gem "importmap-rails"
# Use postgresql as the database for Active Record # Use postgresql as the database for Active Record
gem "pg", "~> 1.1" gem "pg", "~> 1.1"
gem 'pg_search'
gem "pg_search"
# Use the Puma web server [https://github.com/puma/puma] # Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
gem 'sidekiq'
gem "puma"
gem "sidekiq"
gem "redis", ">= 4.0.1" gem "redis", ">= 4.0.1"
# Build JSON APIs with ease [https://github.com/rails/jbuilder] # Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder" gem "jbuilder"
gem 'mobility', '~> 1.3.0.rc1'
gem 'kaminari'
gem 'ancestry'
gem 'acts_as_list'
gem 'date_validator'
gem 'premailer-rails'
gem 'dkim'
gem "mobility", "~> 1.3.0.rc1"
gem "kaminari"
gem "ancestry"
gem "acts_as_list"
gem "date_validator"
gem "premailer-rails"
gem "dkim"
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis" # gem "kredis"
# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", require: false
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.7" gem "bcrypt", "~> 3.1.7"
@ -53,24 +54,10 @@ gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
gem "image_processing", "~> 1.2" gem "image_processing", "~> 1.2"
# group :development, :test do
# # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
# gem "debug", platforms: %i[ mri windows ]
# end
group :development do group :development do
# Use console on exceptions pages [https://github.com/rails/web-console] # Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console" gem "web-console"
# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
gem "rubocop-rails-omakase", require: false
end end
# group :test do
# # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
# gem "capybara"
# gem "selenium-webdriver"
# end

+ 356
- 136
Gemfile.lock View File

@ -1,29 +1,31 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.3)
actionpack (= 8.0.3)
activesupport (= 8.0.3)
action_text-trix (2.1.17)
railties
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)
actionpack (= 8.0.3)
activejob (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
actionmailbox (8.1.2)
actionpack (= 8.1.2)
activejob (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.3)
actionpack (= 8.0.3)
actionview (= 8.0.3)
activejob (= 8.0.3)
activesupport (= 8.0.3)
actionmailer (8.1.2)
actionpack (= 8.1.2)
actionview (= 8.1.2)
activejob (= 8.1.2)
activesupport (= 8.1.2)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.3)
actionview (= 8.0.3)
activesupport (= 8.0.3)
actionpack (8.1.2)
actionview (= 8.1.2)
activesupport (= 8.1.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@ -31,106 +33,114 @@ 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)
actionpack (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
actiontext (8.1.2)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.3)
activesupport (= 8.0.3)
actionview (8.1.2)
activesupport (= 8.1.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.0.3)
activesupport (= 8.0.3)
activejob (8.1.2)
activesupport (= 8.1.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.3)
activesupport (= 8.0.3)
activerecord (8.0.3)
activemodel (= 8.0.3)
activesupport (= 8.0.3)
activemodel (8.1.2)
activesupport (= 8.1.2)
activerecord (8.1.2)
activemodel (= 8.1.2)
activesupport (= 8.1.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.3)
actionpack (= 8.0.3)
activejob (= 8.0.3)
activerecord (= 8.0.3)
activesupport (= 8.0.3)
activestorage (8.1.2)
actionpack (= 8.1.2)
activejob (= 8.1.2)
activerecord (= 8.1.2)
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)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1) uri (>= 0.13.1)
acts_as_list (1.2.4)
acts_as_list (1.2.6)
activerecord (>= 6.1) activerecord (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ancestry (4.3.3)
addressable (2.8.9)
public_suffix (>= 2.0.2, < 8.0)
ancestry (5.1.0)
activerecord (>= 5.2.6) activerecord (>= 5.2.6)
logger
ast (2.4.3)
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.4.1)
bigdecimal (3.3.1)
bcrypt (3.1.22)
bigdecimal (4.0.1)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.6)
bootsnap (1.23.0)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.3.0) builder (3.3.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
concurrent-ruby (1.3.6)
connection_pool (3.0.2)
crass (1.0.6) crass (1.0.6)
css_parser (1.21.1)
css_parser (2.0.0)
addressable addressable
date (3.4.1)
date (3.5.1)
date_validator (0.12.0) date_validator (0.12.0)
activemodel (>= 3) activemodel (>= 3)
activesupport (>= 3) activesupport (>= 3)
dkim (1.1.0) dkim (1.1.0)
dotenv (3.1.8)
dotenv-rails (3.1.8)
dotenv (= 3.1.8)
dotenv (3.2.0)
dotenv-rails (3.2.0)
dotenv (= 3.2.0)
railties (>= 6.1) railties (>= 6.1)
drb (2.2.3) drb (2.2.3)
erb (5.1.1)
erb (6.0.2)
erubi (1.13.1) erubi (1.13.1)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86-linux-gnu)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.3-aarch64-linux-gnu)
ffi (1.17.3-aarch64-linux-musl)
ffi (1.17.3-arm-linux-gnu)
ffi (1.17.3-arm-linux-musl)
ffi (1.17.3-arm64-darwin)
ffi (1.17.3-x86_64-darwin)
ffi (1.17.3-x86_64-linux-gnu)
ffi (1.17.3-x86_64-linux-musl)
globalid (1.3.0) globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
htmlentities (4.3.4)
i18n (1.14.7)
htmlentities (4.4.2)
i18n (1.14.8)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.14.0) image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6) mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
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)
io-console (0.8.1)
irb (1.15.2)
io-console (0.8.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)
json (2.15.1)
json (2.19.2)
json-schema (6.2.0)
addressable (~> 2.8)
bigdecimal (>= 3.1, < 5)
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2) kaminari-actionview (= 1.2.2)
@ -143,26 +153,32 @@ GEM
activerecord activerecord
kaminari-core (= 1.2.2) kaminari-core (= 1.2.2)
kaminari-core (1.2.2) kaminari-core (1.2.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0) logger (1.7.0)
loofah (2.24.1)
loofah (2.25.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1)
mail (2.9.0)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.1.0) marcel (1.1.0)
mcp (0.8.0)
json-schema (>= 4.1)
mini_magick (5.3.1) mini_magick (5.3.1)
logger logger
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (5.26.0)
minitest (6.0.2)
drb (~> 2.0)
prism (~> 1.5)
mobility (1.3.2) mobility (1.3.2)
i18n (>= 0.6.10, < 2) i18n (>= 0.6.10, < 2)
request_store (~> 1.0) request_store (~> 1.0)
msgpack (1.8.0) msgpack (1.8.0)
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)
@ -171,32 +187,41 @@ GEM
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
nio4r (2.7.5)
nokogiri (1.19.1-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.1-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-aarch64-linux-gnu)
nokogiri (1.19.1-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-arm-linux-gnu)
nokogiri (1.19.1-arm-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-arm64-darwin)
nokogiri (1.19.1-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-x86_64-darwin)
nokogiri (1.19.1-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-gnu)
nokogiri (1.19.1-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
openssl (3.3.1)
pg (1.6.2)
pg (1.6.2-aarch64-linux)
pg (1.6.2-arm64-darwin)
pg (1.6.2-x86_64-darwin)
pg (1.6.2-x86_64-linux)
nokogiri (1.19.1-x86_64-linux-musl)
racc (~> 1.4)
openssl (4.0.1)
parallel (1.27.0)
parser (3.3.10.2)
ast (~> 2.4.1)
racc
pg (1.6.3)
pg (1.6.3-aarch64-linux)
pg (1.6.3-aarch64-linux-musl)
pg (1.6.3-arm64-darwin)
pg (1.6.3-x86_64-darwin)
pg (1.6.3-x86_64-linux)
pg (1.6.3-x86_64-linux-musl)
pg_search (2.3.7) pg_search (2.3.7)
activerecord (>= 6.1) activerecord (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
pp (0.6.3) pp (0.6.3)
prettyprint prettyprint
premailer (1.27.0)
premailer (1.29.0)
addressable addressable
css_parser (>= 1.19.0) css_parser (>= 1.19.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
@ -205,109 +230,153 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prettyprint (0.2.0) prettyprint (0.2.0)
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
public_suffix (6.0.2)
puma (7.0.4)
public_suffix (7.0.5)
puma (7.2.0)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.2.3)
rack (3.2.5)
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)
actioncable (= 8.0.3)
actionmailbox (= 8.0.3)
actionmailer (= 8.0.3)
actionpack (= 8.0.3)
actiontext (= 8.0.3)
actionview (= 8.0.3)
activejob (= 8.0.3)
activemodel (= 8.0.3)
activerecord (= 8.0.3)
activestorage (= 8.0.3)
activesupport (= 8.0.3)
rails (8.1.2)
actioncable (= 8.1.2)
actionmailbox (= 8.1.2)
actionmailer (= 8.1.2)
actionpack (= 8.1.2)
actiontext (= 8.1.2)
actionview (= 8.1.2)
activejob (= 8.1.2)
activemodel (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.3)
railties (= 8.1.2)
rails-dom-testing (2.3.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
rails-html-sanitizer (1.7.0)
loofah (~> 2.25)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.0.3)
actionpack (= 8.0.3)
activesupport (= 8.0.3)
railties (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2) tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rake (13.3.0)
rdoc (6.15.0)
rainbow (3.1.1)
rake (13.3.1)
rdoc (7.2.0)
erb erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort tsort
redis (5.4.1) redis (5.4.1)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.26.1)
redis-client (0.28.0)
connection_pool connection_pool
reline (0.6.2)
regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.7.0) request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
ruby-vips (2.2.5)
rubocop (1.85.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
mcp (~> 0.6)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.49.1)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-performance (1.26.1)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
rubocop-rails (2.34.3)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rails-omakase (1.1.0)
rubocop (>= 1.72)
rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0)
ruby-vips (2.3.0)
ffi (~> 1.12) ffi (~> 1.12)
logger logger
securerandom (0.4.1) securerandom (0.4.1)
sidekiq (8.0.8)
connection_pool (>= 2.5.0)
json (>= 2.9.0)
logger (>= 1.6.2)
rack (>= 3.1.0)
redis-client (>= 0.23.2)
sidekiq (8.1.1)
connection_pool (>= 3.0.0)
json (>= 2.16.0)
logger (>= 1.7.0)
rack (>= 3.2.0)
redis-client (>= 0.26.0)
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.7)
thor (1.4.0)
timeout (0.4.3)
stringio (3.2.0)
thor (1.5.0)
thruster (0.1.19)
thruster (0.1.19-aarch64-linux)
thruster (0.1.19-arm64-darwin)
thruster (0.1.19-x86_64-darwin)
thruster (0.1.19-x86_64-linux)
timeout (0.6.1)
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)
uri (1.0.4)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
uri (1.1.1)
useragent (0.16.11) useragent (0.16.11)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
web-console (4.3.0)
actionview (>= 8.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0)
railties (>= 8.0.0)
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.5)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
arm-linux
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin arm64-darwin
x86-linux
x86_64-darwin x86_64-darwin
x86_64-linux x86_64-linux
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES DEPENDENCIES
acts_as_list acts_as_list
@ -327,16 +396,167 @@ DEPENDENCIES
pg_search pg_search
premailer-rails premailer-rails
propshaft propshaft
puma (>= 5.0)
rails (~> 8.0.2)
puma
rails (~> 8.1.2)
redis (>= 4.0.1) redis (>= 4.0.1)
rubocop-rails-omakase
sidekiq sidekiq
stimulus-rails stimulus-rails
thruster
turbo-rails turbo-rails
web-console web-console
CHECKSUMS
action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951
actioncable (8.1.2) sha256=dc31efc34cca9cdefc5c691ddb8b4b214c0ea5cd1372108cbc1377767fb91969
actionmailbox (8.1.2) sha256=058b2fb1980e5d5a894f675475fcfa45c62631103d5a2596d9610ec81581889b
actionmailer (8.1.2) sha256=f4c1d2060f653bfe908aa7fdc5a61c0e5279670de992146582f2e36f8b9175e9
actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423
actiontext (8.1.2) sha256=0bf57da22a9c19d970779c3ce24a56be31b51c7640f2763ec64aa72e358d2d2d
actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b
activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e
activerecord (8.1.2) sha256=acfbe0cadfcc50fa208011fe6f4eb01cae682ebae0ef57145ba45380c74bcc44
activestorage (8.1.2) sha256=8a63a48c3999caeee26a59441f813f94681fc35cc41aba7ce1f836add04fba76
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
ancestry (5.1.0) sha256=8a073cf6f7e306eeed36af72595abd19602ef4a197bf4beda2f31cf8f55de27b
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
bootsnap (1.23.0) sha256=c1254f458d58558b58be0f8eb8f6eec2821456785b7cdd1e16248e2020d3f214
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
css_parser (2.0.0) sha256=af5c759a127b125b635006a6c6c2e05b96a1ebdeec21b3c415fd5f09ec714a0a
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058
dkim (1.1.0) sha256=74f2e3075c89cd2967995f3ee293da9dccccffee21ee593715a068ab39a717f8
dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
dotenv-rails (3.2.0) sha256=657e25554ba622ffc95d8c4f1670286510f47f2edda9f68293c3f661b303beab
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068
ffi (1.17.3-aarch64-linux-musl) sha256=020b33b76775b1abacc3b7d86b287cef3251f66d747092deec592c7f5df764b2
ffi (1.17.3-arm-linux-gnu) sha256=5bd4cea83b68b5ec0037f99c57d5ce2dd5aa438f35decc5ef68a7d085c785668
ffi (1.17.3-arm-linux-musl) sha256=0d7626bb96265f9af78afa33e267d71cfef9d9a8eb8f5525344f8da6c7d76053
ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f
ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5
ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
htmlentities (4.4.2) sha256=bbafbdf69f2eca9262be4efef7e43e6a1de54c95eb600f26984f71d2fe96c5c3
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
image_processing (1.14.0) sha256=754cc169c9c262980889bec6bfd325ed1dafad34f85242b5a07b60af004742fb
importmap-rails (2.2.3) sha256=7101be2a4dc97cf1558fb8f573a718404c5f6bcfe94f304bf1f39e444feeb16a
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
jbuilder (2.14.1) sha256=4eb26376ff60ef100cb4fd6fd7533cd271f9998327e86adf20fd8c0e69fabb42
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e
kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909
kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430
kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
mobility (1.3.2) sha256=32fbbb0e53118ef42de20daa6ac94dbb758c628874092eba311b968a1e1d757b
msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
net-imap (0.6.3) sha256=9bab75f876596d09ee7bf911a291da478e0cd6badc54dfb82874855ccc82f2ad
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
nokogiri (1.19.1-aarch64-linux-gnu) sha256=cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32
nokogiri (1.19.1-aarch64-linux-musl) sha256=1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5
nokogiri (1.19.1-arm-linux-gnu) sha256=0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3
nokogiri (1.19.1-arm-linux-musl) sha256=3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d
nokogiri (1.19.1-arm64-darwin) sha256=dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e
nokogiri (1.19.1-x86_64-darwin) sha256=7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf
nokogiri (1.19.1-x86_64-linux-gnu) sha256=1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a
nokogiri (1.19.1-x86_64-linux-musl) sha256=4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23
openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99
pg (1.6.3-aarch64-linux) sha256=0698ad563e02383c27510b76bf7d4cd2de19cd1d16a5013f375dd473e4be72ea
pg (1.6.3-aarch64-linux-musl) sha256=06a75f4ea04b05140146f2a10550b8e0d9f006a79cdaf8b5b130cde40e3ecc2c
pg (1.6.3-arm64-darwin) sha256=7240330b572e6355d7c75a7de535edb5dfcbd6295d9c7777df4d9dddfb8c0e5f
pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6
pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d
pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009
pg_search (2.3.7) sha256=27868941844961e593526b835da20e48995e9b846515e91856084c5bd9ab4f35
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
premailer (1.29.0) sha256=015f30c520701f3d47fd898886a3eaf4e5171efcc5fd239b872efd1ba61d3da0
premailer-rails (1.12.0) sha256=c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
propshaft (1.3.1) sha256=9acc664ef67e819ffa3d95bd7ad4c3623ea799110c5f4dee67fa7e583e74c392
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
rails (8.1.2) sha256=5069061b23dfa8706b9f0159ae8b9d35727359103178a26962b868a680ba7d95
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
railties (8.1.2) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb
rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
sidekiq (8.1.1) sha256=afdf2736883e1e80c1c9b7c47afe0387e1a4997c87170978a45560d35d429151
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
thruster (0.1.19) sha256=3246eb6ea6fe699e89d6eecc4edda856452e4ebeb9d5999791f0e88832369527
thruster (0.1.19-aarch64-linux) sha256=cfabff204f655359675c3f0e3e3940c6d1f110b765d0d48f5281077b8bb65325
thruster (0.1.19-arm64-darwin) sha256=2e1994341ad1076cea438a5a86b0dc126dc6e66f5a67f2dc077779b73e9fb6e3
thruster (0.1.19-x86_64-darwin) sha256=f026e833dc67048843919ce31a8b12b97b532df41a749d66b72f282efd2a0693
thruster (0.1.19-x86_64-linux) sha256=7cddc1bd0c4fb2e26cc4878dc1f4eb7e883a45932e8d1cacda71b1aba1a645b6
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4
websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
RUBY VERSION RUBY VERSION
ruby 3.4.1p0
ruby 3.4.9
BUNDLED WITH BUNDLED WITH
2.5.17
4.0.8

+ 12
- 12
README DB.txt View File

@ -2,27 +2,27 @@ Development
-------------------- --------------------
Local Local
dropdb -U mattias ikea_foundtation_week_2025
createdb ikea_foundtation_week_2025
pg_restore -Fc --create --dbname=ikea_foundtation_week_2025 --username=mattias db20150901.bak
dropdb -U mattias ikea_foundtation_week_2026
createdb ikea_foundtation_week_2026
pg_restore -Fc --create --dbname=ikea_foundtation_week_2026 --username=mattias db20150901.bak
DB DB
------------------- -------------------
sudo -u postgres createuser --interactive ikea_foundation_2025
sudo -u postgres createdb -O ikea_foundation_2025 ikea_foundtation_week_2025
sudo -u postgres createuser --interactive ikea_foundation_2026
sudo -u postgres createdb -O ikea_foundation_2026 ikea_foundtation_week_2026
dropdb -U ikea_foundation ikea_foundtation_week_2025
dropdb -U ikea_foundation ikea_foundtation_week_2026
RAILS_ENV=production rails db:migrate RAILS_ENV=production rails db:migrate
rake db:create:all rake db:create:all
CREATE EXTENSION hstore; CREATE EXTENSION hstore;
sudo -u postgres psql -d ikea_foundtation_week_2025
sudo -u postgres psql -d ikea_foundtation_week_2026
pg_dump -U ikea_foundation -Fc ikea_foundtation_week_2025 > db20140909.bak
pg_dump -U ikea_foundation_2026 -Fc ikea_foundtation_week_2026 > db20140909.dump
sudo -i -u postgres sudo -i -u postgres
@ -30,8 +30,8 @@ psql
Dump DEV Dump DEV
------------------- -------------------
pg_dump -U mattias -Fc ikea_foundtation_week_2025 > db20140909.bak
pg_dump -U mattias ikea_foundtation_week_2025 > db20140909.sql
pg_dump -U mattias -Fc ikea_foundtation_week_2026 > db20140909.bak
pg_dump -U mattias ikea_foundtation_week_2026 > db20140909.sql
pg_restore -Fc --dbname=ikea_foundtation_week_2025 --username=ikea_foundation dumps/db20230209.bak
pg_restore -Fc --create --dbname=ikea_foundtation_week_2025 --username=ikea_foundation db20150901.bak
pg_restore -Fc --dbname=ikea_foundtation_week_2026 --username=ikea_foundation dumps/db20230209.bak
pg_restore -Fc --create --dbname=ikea_foundtation_week_2026 --username=ikea_foundation db20150901.bak

+ 3
- 3
README.txt View File

@ -4,9 +4,9 @@ RAILS_ENV=production bundle exec rake assets:precompile
RAILS_ENV=production bundle exec rake assets:clean RAILS_ENV=production bundle exec rake assets:clean
git clone https://git.oncotype.dk/ikea-foundation/week_2024.git week_2025
git clone https://mattias:zibxa7-qozsej-qoksaM@git.oncotype.dk/ikea-foundation/week_2024.git week_2026
rails new --skip-docker --skip-action-mailbox --skip-action-text --skip-action-cable --database=postgresql week_2025
rails new --skip-docker --skip-action-mailbox --skip-action-text --skip-action-cable --database=postgresql week_2026
Credentials Credentials
@ -16,7 +16,7 @@ EDITOR=vi rails credentials:edit
SSL SSL
------------------- -------------------
sudo certbot certonly --webroot -w /home/ikea_foundation/week_2025/public -d ikea-foundation-week-2025.onc.dk
sudo certbot certonly --webroot -w /home/ikea_foundation/week_2026/public -d ikea-foundation-week-2026.onc.dk
Gem Gem


+ 0
- 42
app/controllers/answers_controller.rb View File

@ -1,42 +0,0 @@
class AnswersController < ApplicationController
include QuizHelperMethods
# POST /q/:id/answer
def create
not_found unless question
@answer = Answer.find_or_initialize_by(player_id: current_player.id, node_id: question.id)
@answer.update(answer_params)
respond_to do |format|
if @answer.save
current_player.update_answer_cache
format.html { redirect_to url_for(controller: 'questions', action: 'answer', id: params[:id]) }
else
format.html { render 'questions/show', status: :unprocessable_entity }
end
end
end
def update
create
end
private
# Only allow a list of trusted parameters through.
def answer_params
params.require(:answer).permit(
:value
)
rescue ActionController::ParameterMissing
{}
end
end

+ 1
- 4
app/controllers/concerns/quiz_helper_methods.rb View File

@ -12,7 +12,6 @@ module QuizHelperMethods
:question_index, :question_index,
:player_question_answer, :player_question_answer,
:result_node :result_node
end end
@ -50,7 +49,7 @@ private
def require_player! def require_player!
unless player_present? unless player_present?
redirect_to url_for(controller: 'players', action: 'new')
redirect_to url_for(controller: "players", action: "new")
end end
end end
@ -68,6 +67,4 @@ private
def player_present? def player_present?
current_player.present? current_player.present?
end end
end end

+ 6
- 9
app/controllers/languages_controller.rb View File

@ -1,5 +1,4 @@
class LanguagesController < ApplicationController class LanguagesController < ApplicationController
before_action :set_locale_to_default, only: :index before_action :set_locale_to_default, only: :index
before_action :set_locale, only: :show before_action :set_locale, only: :show
before_action :set_node before_action :set_node
@ -7,7 +6,6 @@ class LanguagesController < ApplicationController
helper_method :accept_language helper_method :accept_language
def index def index
not_found unless @node not_found unless @node
end end
@ -42,17 +40,17 @@ private
def first_matching_language def first_matching_language
accept_languages = request.env['HTTP_ACCEPT_LANGUAGE'].to_s.split(',').map { |l|
lang, q_factor = l.split(';q=')
[lang, (q_factor || '1').to_f]
accept_languages = request.env["HTTP_ACCEPT_LANGUAGE"].to_s.split(",").map { |l|
lang, q_factor = l.split(";q=")
[ lang, (q_factor || "1").to_f ]
}.sort_by { |_, q| -q }.map(&:first) }.sort_by { |_, q| -q }.map(&:first)
available_languages = I18n.t('languages').keys.map(&:to_s)
available_languages = I18n.t("languages").keys.map(&:to_s)
accept_languages.each do |lang| accept_languages.each do |lang|
return lang if available_languages.include?(lang) return lang if available_languages.include?(lang)
lang_part = lang.split('-').first
lang_part = lang.split("-").first
if available_languages.include?(lang_part) if available_languages.include?(lang_part)
return lang_part return lang_part
else else
@ -60,12 +58,11 @@ private
return wildcard_match if wildcard_match return wildcard_match if wildcard_match
end end
end end
'en'
"en"
end end
def set_locale_to_default def set_locale_to_default
I18n.locale = I18n.default_locale I18n.locale = I18n.default_locale
end end
end end

+ 0
- 42
app/controllers/players_controller.rb View File

@ -1,42 +0,0 @@
class PlayersController < ApplicationController
include QuizHelperMethods
skip_before_action :require_player!
# GET /player
def new
reset_session
Current.player = nil
@player = Player.new
end
# POST /player
def create
@player = Player.new(player_params.merge(locale: I18n.locale))
respond_to do |format|
if @player.save
session[:player_id] = @player.id
format.html { redirect_to url_for(controller: 'questions', action: 'show', id: 1) }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
private
# Only allow a list of trusted parameters through.
def player_params
params.require(:player).permit(
:name
)
end
end

+ 0
- 94
app/controllers/questions_controller.rb View File

@ -1,94 +0,0 @@
class QuestionsController < ApplicationController
include QuizHelperMethods
def show
not_found unless question
@answer = Answer.find_or_initialize_by(player_id: current_player.id, node_id: question.id)
end
def answer
@answer = Answer.find_by(node_id: question.id, player_id: current_player.id)
@question_answer = question.attachments.with_text.each_slice(2).to_a[@answer.value]
end
def score
not_found if result_node.blank? or current_player.answers.count < questions.size
planet_score = 0
current_player.answers.ordered.each_with_index do |answer, i|
case i
when 0, 3
planet_score += 1 if answer.value == 0
else
planet_score += 1 if answer.value == 1
end
end
current_player.update(score: planet_score)
quiz_result = QuizResult.create!(
player_name: current_player.name,
score: planet_score,
stats: current_player.stats,
locale: I18n.locale
)
redirect_to url_for(action: 'result', sid: quiz_result.share_id)
end
def result
not_found if result_node.blank? or current_player.score.blank?
people_score = questions.count - current_player.score
score_diff = people_score - current_player.score
attachment_index = case
when score_diff >= 2
0 # People
when score_diff <= -2
1 # Planet
else
2 # Balanced
end
@result_attachment = result_node.attachments.offset(attachment_index).first
@stats_attachment = result_node.attachments.offset(3).first
end
def shared_result
@quiz_result = QuizResult.find_by!(share_id: params[:id])
people_score = questions.count - @quiz_result.score
score_diff = people_score - @quiz_result.score
attachment_index = case
when score_diff >= 2
0 # People
when score_diff <= -2
1 # Planet
else
2 # Balanced
end
@result_attachment = result_node.attachments.offset(attachment_index).first
@stats_attachment = result_node.attachments.offset(3).first
end
end

+ 3
- 8
app/controllers/site_controller.rb View File

@ -1,5 +1,4 @@
class SiteController < ApplicationController class SiteController < ApplicationController
before_action :set_locale before_action :set_locale
@ -15,7 +14,7 @@ class SiteController < ApplicationController
def page def page
@node = Node.find_by!("url->>'#{I18n.locale}' = ?", url_from_param) @node = Node.find_by!("url->>'#{I18n.locale}' = ?", url_from_param)
not_found and return unless (@node and @node.viewable?)
not_found and return unless @node and @node.viewable?
# Redirect to first child if index? # Redirect to first child if index?
redirect_to @node.children.viewable.ordered.first.url and return if @node.index? and @node.children.viewable.any? redirect_to @node.children.viewable.ordered.first.url and return if @node.index? and @node.children.viewable.any?
@ -31,13 +30,9 @@ private
def url_from_param def url_from_param
return '' unless params[:url]
return "" unless params[:url]
# return File.join('', params[:url]) if I18n.default_locale == I18n.locale # return File.join('', params[:url]) if I18n.default_locale == I18n.locale
File.join('', I18n.locale.to_s, params[:url])
File.join("", I18n.locale.to_s, params[:url])
end end
end end

+ 0
- 2
app/helpers/answers_helper.rb View File

@ -1,2 +0,0 @@
module AnswersHelper
end

+ 0
- 2
app/helpers/players_helper.rb View File

@ -1,2 +0,0 @@
module PlayersHelper
end

+ 0
- 99
app/helpers/questions_helper.rb View File

@ -1,99 +0,0 @@
module QuestionsHelper
def decorate_divs_for_typewriting_effect(html)
doc = Nokogiri::HTML.fragment(html, 'utf-8')
doc.xpath('./*[position() > last()-2]').each do |div|
div['class'] = 'typewriter-text'
end
doc.to_html.html_safe
end
def image_orientation(file_attachment)
return nil unless file_attachment&.attached?
metadata = file_attachment.blob.metadata
width = metadata[:width] || 0
height = metadata[:height] || 0
if width > height
:landscape
elsif height > width
:portrait
else
:square
end
end
def responsive_picture_tag_for_question(question)
# assets = question.assets.includes(file_attachment: :blob).to_a
assets = question.assets.includes(file_attachment: :blob).select{ |asset| asset.file.image? }.to_a
# Find one landscape and one portrait image
landscape_asset = assets.find { |asset| image_orientation(asset.file) == :landscape }
portrait_asset = assets.find { |asset| image_orientation(asset.file) == :portrait }
# Fall back to any image if specific orientation not found
landscape_asset ||= portrait_asset
portrait_asset ||= landscape_asset
# If we have at least one image
if landscape_asset || portrait_asset
primary_asset = landscape_asset || portrait_asset
# If we have both orientations, create a responsive picture tag
if landscape_asset && portrait_asset && landscape_asset != portrait_asset
render_responsive_picture(landscape_asset, portrait_asset)
else
# Just one orientation available, create a simple picture tag
render_simple_picture(primary_asset)
end
else
# No images available
content_tag(:div, "No images available", class: "no-images")
end
end
private
def render_responsive_picture(landscape_asset, portrait_asset)
content_tag(:picture) do
concat(
content_tag(:source, "",
media: "(orientation: portrait)",
srcset: rails_storage_proxy_path(portrait_asset.file.variant(resize_to_limit: [800, nil])) + " 800w, " +
rails_storage_proxy_path(portrait_asset.file.variant(resize_to_limit: [1600, nil])) + " 1600w, " +
rails_storage_proxy_path(portrait_asset.file.variant(resize_to_limit: [2400, nil])) + " 2400w",
sizes: "100vw"
)
)
concat(
image_tag(
rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [800, nil])),
srcset: rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [800, nil])) + " 800w, " +
rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [1600, nil])) + " 1600w, " +
rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [2400, nil])) + " 2400w",
sizes: "100vw",
'data-controller': "image"
)
)
end
end
def render_simple_picture(asset)
content_tag(:picture) do
image_tag(
rails_storage_proxy_path(asset.file.variant(resize_to_limit: [800, nil])),
srcset: rails_storage_proxy_path(asset.file.variant(resize_to_limit: [800, nil])) + " 800w, " +
rails_storage_proxy_path(asset.file.variant(resize_to_limit: [1600, nil])) + " 1600w, " +
rails_storage_proxy_path(asset.file.variant(resize_to_limit: [2400, nil])) + " 2400w",
sizes: "100vw",
'data-controller': "image"
)
end
end
end

+ 1
- 1
app/javascript/controllers/select_controller.js View File

@ -1,5 +1,5 @@
import { Controller } from "@hotwired/stimulus" import { Controller } from "@hotwired/stimulus"
import TomSelect from "tom-select";
import TomSelect from "tom-select/dist/js/tom-select.base.min.js";
export default class extends Controller { export default class extends Controller {


+ 0
- 11
app/models/answer.rb View File

@ -1,11 +0,0 @@
class Answer < ApplicationRecord
belongs_to :player
belongs_to :node
validates :value, presence: true
validates :value, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
scope :ordered, -> { joins(:node).order('nodes.position ASC, nodes.ancestry ASC') }
end

+ 0
- 28
app/models/player.rb View File

@ -1,28 +0,0 @@
class Player < ApplicationRecord
validates_presence_of :name, message: ->(object, data) { I18n.t('please_type_your_name_to_continue') }
has_many :answers, dependent: :destroy
def update_answer_cache
self.update_column(:answer_cache, self.answers.ordered.pluck(:value))
end
def stats
total_players_with_score = Player.where.not(score: nil).count
n_players_with_matching_score = Player.where(score: self.score).count
# Calculate percentage
if total_players_with_score > 0
percentage = (n_players_with_matching_score.to_f / total_players_with_score) * 100
percentage_string = percentage.round.to_s
else
percentage_string = "0"
end
percentage_string
end
end

+ 0
- 15
app/models/quiz_result.rb View File

@ -1,15 +0,0 @@
class QuizResult < ApplicationRecord
validates :share_id, presence: true, uniqueness: true
validates :player_name, :stats, :score, presence: true
before_validation :generate_share_id, on: :create
private
def generate_share_id
self.share_id = SecureRandom.urlsafe_base64(8) if share_id.blank?
# Ensure uniqueness (very unlikely collision but good practice)
generate_share_id if QuizResult.exists?(share_id: share_id)
end
end

+ 1
- 14
app/views/languages/index.html.erb View File

@ -1,20 +1,7 @@
<%- <%-
content_for :title, t('project_name') content_for :title, t('project_name')
assets = @node.assets.includes(file_attachment: :blob).select{ |asset| asset.file.image? }.to_a
# Find one landscape and one portrait image
landscape_asset = assets.find { |asset| image_orientation(asset.file) == :landscape }
portrait_asset = assets.find { |asset| image_orientation(asset.file) == :portrait }
landscape_asset ||= portrait_asset
portrait_asset ||= landscape_asset
body_styles = []
body_styles << "--bg-landscape: url(#{rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [2400, nil]))});"
body_styles << "--bg-portrait: url(#{rails_storage_proxy_path(portrait_asset.file.variant(resize_to_limit: [1600, nil]))});"
content_for :body_style, body_styles.join(' ')
%> %>


+ 3
- 14
app/views/layouts/application.html.erb View File

@ -22,21 +22,10 @@
<%= stylesheet_link_tag "application" %> <%= stylesheet_link_tag "application" %>
<%= javascript_include_tag 'gsap/gsap.min.js' %>
<%= frontend_javascript_importmap_tags %w'application @hotwired/turbo-rails @hotwired/stimulus locale_controller image_controller quiz_preloader plausible_controller' %>
<script async src="https://plausible.io/js/pa-kq4MBoO3HcKuUqds__gr_.js"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
</head> </head>
<% if content_for? :body_style %>
<body style="<%= yield :body_style %>">
<% else %>
<body>
<% end %>
<body>
<header data-quiz-images="<%= quiz_image_urls.to_json if defined?(quiz_image_urls) %>"> <header data-quiz-images="<%= quiz_image_urls.to_json if defined?(quiz_image_urls) %>">
<%= link_to svg('ikea-foundation-203x22'), url_for(controller: 'languages', action: (I18n.default_locale == I18n.locale ? 'index' : 'show')) %> <%= link_to svg('ikea-foundation-203x22'), url_for(controller: 'languages', action: (I18n.default_locale == I18n.locale ? 'index' : 'show')) %>
@ -46,6 +35,6 @@
<%= yield %> <%= yield %>
</main> </main>
<footer><span>© Inter IKEA Systems B.V. 2025</span></footer>
<footer><span>© Inter IKEA Systems B.V. 2026</span></footer>
</body> </body>
</html> </html>

+ 0
- 39
app/views/players/new.html.erb View File

@ -1,39 +0,0 @@
<%-
content_for :title, t('what_is_your_name')
%>
<%= form_with model: @player, url: url_for(controller: 'players', action: 'create') do |form| %>
<div class="question-container with-backdrop">
<div class="animation-element">
<div class="question-header">
<div class="typewriter-text"><%= t 'what_is_your_name' %></div>
</div>
</div>
<% if form.object.errors.any? %>
<div class="animation-element">
<div class="error">
<% form.object.errors.messages.each do |k, v| %>
<%= v.join(', ') %>
<% end %>
</div>
</div>
<% end %>
<div class="animation-element">
<%= form.label :name, class: "question-answer" do %>
<%= form.text_field :name, placeholder: t('write_your_name'), autofocus: true, autocomplete: 'off' %>
<% end %>
</div>
<div class="animation-element">
<%= form.button tag.span(t('submit')),
class: form.object.errors.any? ? 'button__base wiggle' : 'button__base' %>
</div>
</div>
<% end %>

+ 0
- 45
app/views/questions/answer.html.erb View File

@ -1,45 +0,0 @@
<%-
content_for :title, question.page_title.blank? ? question.title : question.page_title
content_for :meta_description, question.page_description
%>
<div class="question-container with-backdrop">
<div class="animation-element">
<div class="question-step">
<div><%= question_index %>/<%= questions_size %></div>
</div>
</div>
<div class="animation-element">
<div class="answers-distribution">
<% question.answer_percentages.each do |k,v| %>
<%= tag.div data: { value: v, answered: (k == player_question_answer ? 1 : nil) }, class: 'answer-distribution' do %>
<%= tag.div v, class: 'percentage' %>
<%= tag.div t('of_people_worldwide_think_just_lik_you'), class: 'explanation' if k == player_question_answer %>
<% end %>
<% end %>
</div>
</div>
<div class="animation-element">
<div class="question-result">
<%= @question_answer.last.body.html_safe %>
</div>
</div>
<div class="animation-element">
<% if question == questions.last %>
<%= button_to tag.span(t('see_results')),
url_for(controller: 'questions', action: 'score'),
class: 'button__base' %>
<%# link_to t('see_results'), url_for(controller: 'questions', action: 'result') %>
<% else %>
<%= link_to tag.span(t('next_question')),
url_for(controller: 'questions', action: 'show', id: question_index.next),
class: 'button__base' %>
<% end %>
</div>
</div>

+ 0
- 41
app/views/questions/result.html.erb View File

@ -1,41 +0,0 @@
<%-
content_for :title, result_node.page_title.blank? ? result_node.title : result_node.page_title
content_for :meta_description, result_node.page_description
%>
<div class="question-container with-backdrop">
<div class="animation-element">
<div class="question-step">
<div><%= t 'results' %></div>
</div>
</div>
<div class="animation-element">
<div class="result-msg">
<%= decorate_divs_for_typewriting_effect @result_attachment&.body&.sub("&lt;name&gt;", html_escape(current_player.name)).html_safe %>
</div>
</div>
<div class="animation-element">
<div class="result-stats">
<%= @stats_attachment&.body&.sub("&lt;score&gt;", current_player.stats).html_safe %>
</div>
</div>
<div class="animation-element">
<div class="result-actions">
<%= link_to tag.span(t('read_more')), t('read_more_link'), class: 'button__base', target: '_blank' %>
<%= button_tag tag.span(t('share_on_story')),
data: {
share_title: t('share_title'),
share_text: t('share_text'),
share_url: url_for(controller: 'questions', action: 'shared_result', id: params[:sid], only_path: false)
},
type: 'button',
class: 'button__base' %>
</div>
</div>
</div>

+ 0
- 41
app/views/questions/shared_result.html.erb View File

@ -1,41 +0,0 @@
<%-
content_for :title, result_node.page_title.blank? ? result_node.title : result_node.page_title
content_for :meta_description, result_node.page_description
%>
<div class="question-container with-backdrop">
<div class="animation-element">
<div class="question-step">
<div><%= t 'results' %></div>
</div>
</div>
<div class="animation-element">
<div class="result-msg">
<%= decorate_divs_for_typewriting_effect @result_attachment&.body&.sub("&lt;name&gt;", html_escape(@quiz_result.player_name)).html_safe %>
</div>
</div>
<div class="animation-element">
<div class="result-stats">
<%= @stats_attachment&.body&.sub("&lt;score&gt;", @quiz_result.stats).html_safe %>
</div>
</div>
<div class="animation-element">
<div class="result-actions">
<%= link_to tag.span(t('read_more')), "#", class: 'button__base' %>
<%# button_tag tag.span(t('share_on_story')),
data: {
share_title: '',
share_text: '',
share_url: url_for(controller: 'questions', action: 'shared_result', id: params[:sid])
},
type: 'button',
class: 'button__base' %>
</div>
</div>
</div>

+ 0
- 57
app/views/questions/show.html.erb View File

@ -1,57 +0,0 @@
<%-
content_for :title, question.page_title.blank? ? question.title : question.page_title
content_for :meta_description, question.page_description
assets = question.assets.includes(file_attachment: :blob).select{ |asset| asset.file.image? }.to_a
if assets.any?
# Find one landscape and one portrait image
landscape_asset = assets.find { |asset| image_orientation(asset.file) == :landscape }
portrait_asset = assets.find { |asset| image_orientation(asset.file) == :portrait }
landscape_asset ||= portrait_asset
portrait_asset ||= landscape_asset
body_styles = []
body_styles << "--bg-landscape: url(#{rails_storage_proxy_path(landscape_asset.file.variant(resize_to_limit: [2400, nil]))});"
body_styles << "--bg-portrait: url(#{rails_storage_proxy_path(portrait_asset.file.variant(resize_to_limit: [1600, nil]))});"
content_for :body_style, body_styles.join(' ')
end
%>
<%= form_with model: @answer, url: url_for(controller: 'answers', action: 'create'), id: 'questionForm' do |form| %>
<div class="question-container">
<div class="animation-element">
<div class="question-step">
<div><%= question_index %>/<%= questions_size %></div>
</div>
</div>
<div class="animation-element">
<div class="question-header">
<div class="typewriter-text"><%= question.title %></div>
</div>
</div>
<div class="animation-element">
<% question.attachments.with_text.each_slice(2).each_with_index do |answer_option, i| %>
<div class="question-answer">
<%= form.label :value, for: nil do %>
<%= tag.div tag.span(sanitize(answer_option.first.body.html_safe, tags: %w"em strong")) %>
<%= form.radio_button :value, i %>
<%- end -%>
</div>
<% end %>
</div>
<div class="animation-element">
<%= form.button tag.span(t('submit')), class: 'button__base', id: 'questionSubmitButton' %>
</div>
</div>
<% end %>

+ 6
- 0
bin/ci 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"

+ 1
- 1
bin/rubocop 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")

+ 1
- 0
bin/setup 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"


+ 5
- 0
bin/thrust View File

@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
load Gem.bin_path("thruster", "thrust")

+ 5
- 4
config/application.rb View File

@ -18,17 +18,17 @@ require "rails/test_unit/railtie"
# you've limited to :test, :development, or :production. # you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
module Week2025
module Week2026
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 8.0
config.load_defaults 8.1
# 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.
config.autoload_lib(ignore: %w[assets tasks]) config.autoload_lib(ignore: %w[assets tasks])
config.autoload_lib(ignore: %w(assets tasks))
config.autoload_lib(ignore: %w[assets tasks])
config.time_zone = "Copenhagen" config.time_zone = "Copenhagen"
@ -36,7 +36,8 @@ module Week2025
config.i18n.available_locales = [:en, :zh, :hr, :cs, :da, :nl, :fi, :fr, :'fr-CA', :de, :hu, :it, :ja, :ko, :nb, :pl, :pt, :ro, :sr, :sk, :sl, :es, :sv, :uk] config.i18n.available_locales = [:en, :zh, :hr, :cs, :da, :nl, :fi, :fr, :'fr-CA', :de, :hu, :it, :ja, :ko, :nb, :pl, :pt, :ro, :sr, :sk, :sl, :es, :sv, :uk]
config.i18n.fallbacks = true config.i18n.fallbacks = true
config.active_storage.routes_prefix = 'storage' #change /rails/active_storage to /storage
# change /rails/active_storage to /storage
config.active_storage.routes_prefix = "storage"
# Configuration for the application, engines, and railties goes here. # Configuration for the application, engines, and railties goes here.
# #


+ 1
- 1
config/cable.yml View File

@ -7,4 +7,4 @@ test:
production: production:
adapter: redis adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: week2025_production
channel_prefix: week2026_production

+ 22
- 0
config/ci.rb View File

@ -0,0 +1,22 @@
# Run using bin/ci
CI.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Importmap vulnerability audit", "bin/importmap audit"
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

+ 6
- 6
config/importmap.rb View File

@ -5,16 +5,16 @@ pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers" pin_all_from "app/javascript/controllers", under: "controllers"
pin "sortablejs" # @1.15.6
pin "sortablejs" # @1.15.7
pin "stimulus-use" # @0.52.3 pin "stimulus-use" # @0.52.3
pin "@rails/request.js", to: "@rails--request.js.js" # @0.0.12
pin "@rails/activestorage", to: "@rails--activestorage.js" # @8.0.300
pin "tom-select", to: "tom-select--dist--js--tom-select.base.min.js.js" # @2.4.3
pin "trix" # @2.1.15
pin "@rails/request.js", to: "@rails--request.js.js" # @0.0.13
pin "@rails/activestorage", to: "@rails--activestorage.js" # @8.1.200
pin "trix" # @2.1.16
pin "tom-select/dist/js/tom-select.base.min.js", to: "tom-select--dist--js--tom-select.base.min.js.js" # @2.4.3
# site_helper # site_helper
pin "application", preload: false pin "application", preload: false
pin "locale_controller", preload: false pin "locale_controller", preload: false
pin "image_controller", preload: false pin "image_controller", preload: false
pin "quiz_preloader", preload: false pin "quiz_preloader", preload: false
pin 'plausible_controller', preload: false
pin "plausible_controller", preload: false

+ 4
- 0
config/initializers/content_security_policy.rb View File

@ -20,6 +20,10 @@
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src style-src) # config.content_security_policy_nonce_directives = %w(script-src style-src)
# #
# # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
# # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
# # config.content_security_policy_nonce_auto = true
#
# # Report violations without enforcing the policy. # # Report violations without enforcing the policy.
# # config.content_security_policy_report_only = true # # config.content_security_policy_report_only = true
# end # end

+ 0
- 70
config/initializers/new_framework_defaults_7_2.rb View File

@ -1,70 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file eases your Rails 7.2 framework defaults upgrade.
#
# Uncomment each configuration one by one to switch to the new default.
# Once your application is ready to run with all new defaults, you can remove
# this file and set the `config.load_defaults` to `7.2`.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
###
# Controls whether Active Job's `#perform_later` and similar methods automatically defer
# the job queuing to after the current Active Record transaction is committed.
#
# Example:
# Topic.transaction do
# topic = Topic.create(...)
# NewTopicNotificationJob.perform_later(topic)
# end
#
# In this example, if the configuration is set to `:never`, the job will
# be enqueued immediately, even though the `Topic` hasn't been committed yet.
# Because of this, if the job is picked up almost immediately, or if the
# transaction doesn't succeed for some reason, the job will fail to find this
# topic in the database.
#
# If `enqueue_after_transaction_commit` is set to `:default`, the queue adapter
# will define the behaviour.
#
# Note: Active Job backends can disable this feature. This is generally done by
# backends that use the same database as Active Record as a queue, hence they
# don't need this feature.
#++
# Rails.application.config.active_job.enqueue_after_transaction_commit = :default
###
# Adds image/webp to the list of content types Active Storage considers as an image
# Prevents automatic conversion to a fallback PNG, and assumes clients support WebP, as they support gif, jpeg, and png.
# This is possible due to broad browser support for WebP, but older browsers and email clients may still not support
# WebP. Requires imagemagick/libvips built with WebP support.
#++
# Rails.application.config.active_storage.web_image_content_types = %w[image/png image/jpeg image/gif image/webp]
###
# Enable validation of migration timestamps. When set, an ActiveRecord::InvalidMigrationTimestampError
# will be raised if the timestamp prefix for a migration is more than a day ahead of the timestamp
# associated with the current time. This is done to prevent forward-dating of migration files, which can
# impact migration generation and other migration commands.
#
# Applications with existing timestamped migrations that do not adhere to the
# expected format can disable validation by setting this config to `false`.
#++
# Rails.application.config.active_record.validate_migration_timestamps = true
###
# Controls whether the PostgresqlAdapter should decode dates automatically with manual queries.
#
# Example:
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date") #=> Date
#
# This query used to return a `String`.
#++
# Rails.application.config.active_record.postgresql_adapter_decode_dates = true
###
# Enables YJIT as of Ruby 3.3, to bring sizeable performance improvements. If you are
# deploying to a memory constrained environment you may want to set this to `false`.
#++
# Rails.application.config.yjit = true

+ 0
- 30
config/initializers/new_framework_defaults_8_0.rb View File

@ -1,30 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file eases your Rails 8.0 framework defaults upgrade.
#
# Uncomment each configuration one by one to switch to the new default.
# Once your application is ready to run with all new defaults, you can remove
# this file and set the `config.load_defaults` to `8.0`.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
###
# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone.
# If set to `:zone`, `to_time` methods will use the timezone of their receivers.
# If set to `:offset`, `to_time` methods will use the UTC offset.
# If `false`, `to_time` methods will convert to the local system UTC offset instead.
#++
# Rails.application.config.active_support.to_time_preserves_timezone = :zone
###
# When both `If-Modified-Since` and `If-None-Match` are provided by the client
# only consider `If-None-Match` as specified by RFC 7232 Section 6.
# If set to `false` both conditions need to be satisfied.
#++
# Rails.application.config.action_dispatch.strict_freshness = true
###
# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks.
#++
# Regexp.timeout = 1

+ 37
- 55
config/nginx.conf View File

@ -1,61 +1,46 @@
# http > https # http > https
server { server {
listen 80; listen 80;
server_name completesentences.ikeafoundation.org ikea-foundation-week-2025.onc.dk;
server_name ikea-foundation-week-2026.onc.dk;
root /home/ikea_foundation_2025/week_2025/public;
root /home/ikea_foundation_2026/week_2026/public;
# Lets encrypt # Lets encrypt
location ~ /.well-known { location ~ /.well-known {
allow all; allow all;
} }
location / {
return 301 https://completesentences.ikeafoundation.org$request_uri;
location / {
return 301 https://ikea-foundation-week-2026.onc.dk$request_uri;
} }
} }
# # https
server {
listen 443 ssl;
http2 on;
server_name ikea-foundation-week-2025.onc.dk;
# keepalive_timeout 300;
keepalive_timeout 5;
client_max_body_size 4G;
ssl_certificate /etc/letsencrypt/live/ikea-foundation-week-2025.onc.dk/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ikea-foundation-week-2025.onc.dk/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
location / {
return 301 https://completesentences.ikeafoundation.org$request_uri;
}
}
# # http > https
# # # https
# server { # server {
# listen 80;
# server_name spotlinks.ikeafoundation.org;
#
# root /home/ikea_foundation_2025/week_2025/public;
# listen 443 ssl;
# http2 on;
#
# server_name ikea-foundation-week-2026.onc.dk;
#
# # keepalive_timeout 300;
# keepalive_timeout 5;
# client_max_body_size 4G;
# #
# # Lets encrypt
# location ~ /.well-known {
# allow all;
# }
# ssl_certificate /etc/letsencrypt/live/ikea-foundation-week-2026.onc.dk/fullchain.pem; # managed by Certbot
# ssl_certificate_key /etc/letsencrypt/live/ikea-foundation-week-2026.onc.dk/privkey.pem; # managed by Certbot
# include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# #
# location / { # location / {
# return 301 https://spotlinks.ikeafoundation.org$request_uri;
# }
# return 301 https://completesentences.ikeafoundation.org$request_uri;
# }
# } # }
# Puma # Puma
upstream puma_ikea_25 {
server unix:///home/ikea_foundation_2025/week_2025/tmp/pids/puma.sock fail_timeout=0;
upstream puma_ikea_26 {
server unix:///home/ikea_foundation_2026/week_2026/tmp/pids/puma.sock fail_timeout=0;
} }
@ -64,40 +49,37 @@ upstream puma_ikea_25 {
server { server {
listen 443 ssl; listen 443 ssl;
http2 on; http2 on;
# server_name spotlinks.ikeafoundation.org;
server_name completesentences.ikeafoundation.org;
server_name ikea-foundation-week-2026.onc.dk;
# keepalive_timeout 300; # keepalive_timeout 300;
keepalive_timeout 5; keepalive_timeout 5;
client_max_body_size 4G; client_max_body_size 4G;
# ssl_certificate /etc/letsencrypt/live/spotlinks.ikeafoundation.org/fullchain.pem; # managed by Certbot
# ssl_certificate_key /etc/letsencrypt/live/spotlinks.ikeafoundation.org/privkey.pem; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/completesentences.ikeafoundation.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/completesentences.ikeafoundation.org/privkey.pem; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ikea-foundation-week-2026.onc.dk/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ikea-foundation-week-2026.onc.dk/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
access_log /home/ikea_foundation_2025/week_2025/log/httpd-access.log;
error_log /home/ikea_foundation_2025/week_2025/log/httpd-errors.log;
access_log /home/ikea_foundation_2026/week_2026/log/httpd-access.log;
error_log /home/ikea_foundation_2026/week_2026/log/httpd-errors.log;
root /home/ikea_foundation_2025/week_2025/public;
root /home/ikea_foundation_2026/week_2026/public;
# App server # App server
location @puma_ikea_25 {
location @puma_ikea_26 {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Forwarded_Proto $scheme; proxy_set_header X-Forwarded_Proto $scheme;
proxy_redirect off; proxy_redirect off;
proxy_pass http://puma_ikea_25;
proxy_pass http://puma_ikea_26;
} }
# Serve static (compiled) assets directly if they exist (for rails production) # Serve static (compiled) assets directly if they exist (for rails production)
location ~ ^/(assets|packs|static)/ { location ~ ^/(assets|packs|static)/ {
try_files $uri @puma_ikea_25;
try_files $uri @puma_ikea_26;
access_log off; access_log off;
gzip_static on; gzip_static on;
@ -114,7 +96,7 @@ server {
} }
# location /cable { # location /cable {
# proxy_pass http://puma_ikea_25;
# proxy_pass http://puma_ikea_26;
# proxy_http_version 1.1; # proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade; # proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade'; # proxy_set_header Connection 'upgrade';
@ -122,13 +104,13 @@ server {
# Send non-static file requests to the app server # Send non-static file requests to the app server
location / { location / {
try_files $uri @puma_ikea_25;
try_files $uri @puma_ikea_26;
} }
# You can override error pages by redirecting the requests to a file in your # You can override error pages by redirecting the requests to a file in your
# application's public folder, if you so desire: # application's public folder, if you so desire:
error_page 500 502 503 504 /500.html; error_page 500 502 503 504 /500.html;
location = /500.html { location = /500.html {
root /home/ikea_foundation_2025/week_2025/public;
root /home/ikea_foundation_2026/week_2026/public;
} }
} }

+ 0
- 14
db/migrate/20250429085114_create_players.rb View File

@ -1,14 +0,0 @@
class CreatePlayers < ActiveRecord::Migration[8.0]
def change
create_table :players do |t|
t.text :name
t.text :locale
t.integer :answer_cache, array: true, default: []
t.timestamps
t.index :answer_cache, using: :gin
end
end
end

+ 0
- 14
db/migrate/20250429121602_create_answers.rb View File

@ -1,14 +0,0 @@
class CreateAnswers < ActiveRecord::Migration[8.0]
def change
create_table :answers do |t|
t.references :player
t.references :node
t.integer :value, default: nil, index: true
t.timestamps
end
end
end

+ 0
- 6
db/migrate/20250522111116_add_score_to_players.rb View File

@ -1,6 +0,0 @@
class AddScoreToPlayers < ActiveRecord::Migration[8.0]
def change
add_column :players, :score, :integer, default: nil
add_index :players, :score
end
end

+ 0
- 15
db/migrate/20250527114853_create_quiz_results.rb View File

@ -1,15 +0,0 @@
class CreateQuizResults < ActiveRecord::Migration[8.0]
def change
create_table :quiz_results do |t|
t.string :share_id, null: false
t.string :player_name, null: false
t.string :stats, null: false
t.string :locale, null: false
t.integer :score, null: false
t.timestamps
end
add_index :quiz_results, :share_id, unique: true
end
end

+ 46
- 46
db/schema.rb 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_05_27_114853) do
ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -19,24 +19,24 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_114853) do
create_enum "user_roles", ["user", "admin"] create_enum "user_roles", ["user", "admin"]
create_table "active_storage_attachments", force: :cascade do |t| create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false t.bigint "blob_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end end
create_table "active_storage_blobs", force: :cascade do |t| create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false t.bigint "byte_size", null: false
t.string "checksum" t.string "checksum"
t.string "content_type"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "filename", null: false
t.string "key", null: false
t.text "metadata"
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end end
@ -47,21 +47,21 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_114853) do
end end
create_table "answers", force: :cascade do |t| create_table "answers", force: :cascade do |t|
t.bigint "player_id"
t.bigint "node_id"
t.integer "value"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.bigint "node_id"
t.bigint "player_id"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "value"
t.index ["node_id"], name: "index_answers_on_node_id" t.index ["node_id"], name: "index_answers_on_node_id"
t.index ["player_id"], name: "index_answers_on_player_id" t.index ["player_id"], name: "index_answers_on_player_id"
t.index ["value"], name: "index_answers_on_value" t.index ["value"], name: "index_answers_on_value"
end end
create_table "assets", force: :cascade do |t| create_table "assets", force: :cascade do |t|
t.text "title"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "tags", default: [], array: true t.text "tags", default: [], array: true
t.text "title"
t.datetime "updated_at", null: false
t.index ["tags"], name: "index_assets_on_tags", using: :gin t.index ["tags"], name: "index_assets_on_tags", using: :gin
end end
@ -70,37 +70,37 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_114853) do
t.integer "attachable_for_id" t.integer "attachable_for_id"
t.text "attachable_for_type" t.text "attachable_for_type"
t.jsonb "body", default: {} t.jsonb "body", default: {}
t.jsonb "url", default: {}
t.jsonb "props", default: {}
t.datetime "created_at", null: false
t.boolean "is_favorite", default: false, null: false t.boolean "is_favorite", default: false, null: false
t.integer "position" t.integer "position"
t.datetime "created_at", null: false
t.jsonb "props", default: {}
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.jsonb "url", default: {}
t.index ["asset_id"], name: "index_attachments_on_asset_id" t.index ["asset_id"], name: "index_attachments_on_asset_id"
t.index ["attachable_for_id", "attachable_for_type"], name: "attachable_for" t.index ["attachable_for_id", "attachable_for_type"], name: "attachable_for"
t.index ["is_favorite"], name: "index_attachments_on_is_favorite" t.index ["is_favorite"], name: "index_attachments_on_is_favorite"
end end
create_table "nodes", force: :cascade do |t| create_table "nodes", force: :cascade do |t|
t.integer "position"
t.integer "status", default: 0
t.integer "template", default: 0
t.integer "ancestry_depth", default: 0
t.string "ancestry", null: false, collation: "C" t.string "ancestry", null: false, collation: "C"
t.jsonb "slug", default: {}
t.jsonb "url", default: {}
t.jsonb "title", default: {}
t.jsonb "page_title", default: {}
t.jsonb "page_description", default: {}
t.jsonb "href", default: {}
t.jsonb "tags", default: {}
t.text "settings", default: [], array: true
t.integer "ancestry_depth", default: 0
t.datetime "created_at", null: false
t.text "excluded_locales", default: [], array: true t.text "excluded_locales", default: [], array: true
t.datetime "expires_at"
t.jsonb "href", default: {}
t.boolean "is_allowlist", default: false t.boolean "is_allowlist", default: false
t.jsonb "page_description", default: {}
t.jsonb "page_title", default: {}
t.integer "position"
t.datetime "published_at" t.datetime "published_at"
t.datetime "expires_at"
t.datetime "created_at", null: false
t.text "settings", default: [], array: true
t.jsonb "slug", default: {}
t.integer "status", default: 0
t.jsonb "tags", default: {}
t.integer "template", default: 0
t.jsonb "title", default: {}
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.jsonb "url", default: {}
t.index ["ancestry"], name: "index_nodes_on_ancestry" t.index ["ancestry"], name: "index_nodes_on_ancestry"
t.index ["ancestry_depth"], name: "index_nodes_on_ancestry_depth" t.index ["ancestry_depth"], name: "index_nodes_on_ancestry_depth"
t.index ["excluded_locales"], name: "index_nodes_on_excluded_locales", using: :gin t.index ["excluded_locales"], name: "index_nodes_on_excluded_locales", using: :gin
@ -115,48 +115,48 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_114853) do
end end
create_table "players", force: :cascade do |t| create_table "players", force: :cascade do |t|
t.text "name"
t.text "locale"
t.integer "answer_cache", default: [], array: true t.integer "answer_cache", default: [], array: true
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "locale"
t.text "name"
t.integer "score" t.integer "score"
t.datetime "updated_at", null: false
t.index ["answer_cache"], name: "index_players_on_answer_cache", using: :gin t.index ["answer_cache"], name: "index_players_on_answer_cache", using: :gin
t.index ["score"], name: "index_players_on_score" t.index ["score"], name: "index_players_on_score"
end end
create_table "quiz_results", force: :cascade do |t| create_table "quiz_results", force: :cascade do |t|
t.string "share_id", null: false
t.string "player_name", null: false
t.string "stats", null: false
t.datetime "created_at", null: false
t.string "locale", null: false t.string "locale", null: false
t.string "player_name", null: false
t.integer "score", null: false t.integer "score", null: false
t.datetime "created_at", null: false
t.string "share_id", null: false
t.string "stats", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["share_id"], name: "index_quiz_results_on_share_id", unique: true t.index ["share_id"], name: "index_quiz_results_on_share_id", unique: true
end end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.text "description"
t.text "email" t.text "email"
t.text "password_digest"
t.datetime "enabled_at"
t.text "firstname" t.text "firstname"
t.datetime "last_logon_at"
t.text "lastname" t.text "lastname"
t.text "title"
t.text "password_digest"
t.text "phone" t.text "phone"
t.text "description"
t.enum "role", default: "user", enum_type: "user_roles" t.enum "role", default: "user", enum_type: "user_roles"
t.datetime "enabled_at"
t.datetime "last_logon_at"
t.datetime "created_at", null: false
t.text "title"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email" t.index ["email"], name: "index_users_on_email"
end end
create_table "verification_codes", force: :cascade do |t| create_table "verification_codes", force: :cascade do |t|
t.bigint "user_id"
t.text "token"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.text "token"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.bigint "user_id"
t.index ["token"], name: "index_verification_codes_on_token" t.index ["token"], name: "index_verification_codes_on_token"
t.index ["user_id"], name: "index_verification_codes_on_user_id" t.index ["user_id"], name: "index_verification_codes_on_user_id"
end end


+ 1
- 1
db/seeds.rb View File

@ -11,4 +11,4 @@ User.create(
) )
Node.create title: 'Week 2025', slug: '', published_at: Time.now, ancestry: '/'
Node.create title: 'Week 2026', slug: '', published_at: Time.now, ancestry: '/'

+ 81
- 0
lib/tasks/quiz_stats.rake View File

@ -0,0 +1,81 @@
# lib/tasks/quiz_stats.rake
namespace :quiz do
desc "Show quiz statistics from Oct 17, 2025"
task stats: :environment do
results = QuizResult.where("created_at >= ?", Date.new(2025, 10, 17))
total = results.count
by_locale = results.group(:locale).count
scores = results.group(:score).count
avg_score = results.average(:score)
# Calculate result categories
questions = Node.at_depth(1).tmpl_article.viewable.ordered.to_a
questions_count = questions.count
result_categories = { "People" => 0, "Planet" => 0, "Balanced" => 0 }
results.each do |qr|
people_score = questions_count - qr.score
score_diff = people_score - qr.score
category = case
when score_diff >= 2
"People"
when score_diff <= -2
"Planet"
else
"Balanced"
end
result_categories[category] += 1
end
puts "Quiz Results from Oct 17, 2025"
puts "=" * 40
puts "Total: #{total}"
puts "\nBy locale:"
by_locale.sort.each do |locale, count|
locale_name = I18n.t(locale, scope: 'languages')
puts " #{locale_name}: #{count}"
end
puts "\nScore distribution:"
scores.sort.each do |score, count|
puts " Score #{score}: #{count}"
end
puts "\nAverage score: #{avg_score&.round(2)}"
puts "\nResult categories:"
result_categories.each do |category, count|
percentage = total > 0 ? (count.to_f / total * 100).round(1) : 0
puts " #{category}: #{count} (#{percentage}%)"
end
# Question by question breakdown
puts "\n\nQuestion breakdown:"
puts "=" * 40
questions.each_with_index do |question, i|
answers = Answer.where(node_id: question.id).where("created_at >= ?", Date.new(2025, 10, 17))
answer_counts = answers.group(:value).count
planet_answer = (i == 0 || i == 3) ? 0 : 1
people_answer = (i == 0 || i == 3) ? 1 : 0
planet_count = answer_counts[planet_answer] || 0
people_count = answer_counts[people_answer] || 0
total_answers = planet_count + people_count
puts "\nQ#{i + 1}: #{question.title}"
if total_answers > 0
planet_pct = (planet_count.to_f / total_answers * 100).round(1)
people_pct = (people_count.to_f / total_answers * 100).round(1)
puts " Planet: #{planet_count} (#{planet_pct}%)"
puts " People: #{people_count} (#{people_pct}%)"
else
puts " No answers"
end
end
end
end

+ 1
- 1
mise.toml View File

@ -1,2 +1,2 @@
[tools] [tools]
ruby = "3.4.1"
ruby = "3.4.9"

BIN
public/icon.png View File

Before After
Width: 512  |  Height: 512  |  Size: 4.1 KiB

+ 3
- 0
public/icon.svg View File

@ -0,0 +1,3 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
<circle cx="256" cy="256" r="256" fill="red"/>
</svg>

+ 2
- 2
vendor/javascript/@rails--activestorage.js
File diff suppressed because it is too large
View File


+ 2
- 2
vendor/javascript/@rails--request.js.js
File diff suppressed because it is too large
View File


+ 24
- 24
vendor/javascript/sortablejs.js
File diff suppressed because it is too large
View File


+ 3
- 3
vendor/javascript/trix.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save