Browse Source

2025

main
Mattias Bodlund 7 months ago
parent
commit
1957dcfeee
62 changed files with 702 additions and 878 deletions
  1. BIN
      .DS_Store
  2. +0
    -1
      .ruby-version
  3. +1
    -0
      .tool-versions
  4. +2
    -2
      Gemfile
  5. +146
    -137
      Gemfile.lock
  6. +11
    -11
      README DB.txt
  7. +1
    -2
      README.txt
  8. +0
    -0
      app/assets/fonts/NotoIKEALatin-Bold.ttf
  9. +0
    -0
      app/assets/fonts/NotoIKEALatin-BoldItalic.ttf
  10. +0
    -0
      app/assets/fonts/NotoIKEALatin-Italic.ttf
  11. +0
    -0
      app/assets/fonts/NotoIKEALatin-Regular.ttf
  12. +0
    -0
      app/assets/images/windmill.png
  13. +15
    -1
      app/assets/stylesheets/assets.css
  14. +2
    -3
      app/controllers/admin/assets_controller.rb
  15. +0
    -230
      app/javascript/application.js
  16. +10
    -8
      app/javascript/controllers/upload_controller.js
  17. +2
    -2
      app/models/node.rb
  18. +1
    -1
      app/models/user.rb
  19. +3
    -4
      app/views/admin/assets/_asset.html.erb
  20. +0
    -10
      app/views/admin/assets/_form.html.erb
  21. +1
    -1
      app/views/admin/assets/_list.html.erb
  22. +2
    -3
      app/views/admin/assets/explore.turbo_stream.erb
  23. +3
    -7
      app/views/admin/assets/upload.turbo_stream.erb
  24. +1
    -21
      app/views/admin/attachments/_attachment.html.erb
  25. +2
    -4
      app/views/layouts/application.html.erb
  26. +7
    -0
      bin/brakeman
  27. +2
    -0
      bin/dev
  28. +8
    -0
      bin/rubocop
  29. +5
    -4
      bin/setup
  30. +5
    -3
      config/application.rb
  31. +10
    -0
      config/cable.yml
  32. +21
    -28
      config/environments/development.rb
  33. +36
    -42
      config/environments/production.rb
  34. +7
    -18
      config/environments/test.rb
  35. +6
    -6
      config/importmap.rb
  36. +0
    -5
      config/initializers/assets.rb
  37. +1
    -1
      config/initializers/filter_parameter_logging.rb
  38. +70
    -0
      config/initializers/new_framework_defaults_7_2.rb
  39. +30
    -0
      config/initializers/new_framework_defaults_8_0.rb
  40. +1
    -1
      config/initializers/premailer_rails.rb
  41. +2
    -2
      config/locales/en.yml
  42. +52
    -48
      config/nginx.conf
  43. +26
    -21
      config/puma.rb
  44. +22
    -0
      db/migrate/20250427131735_add_service_name_to_active_storage_blobs.active_storage.rb
  45. +27
    -0
      db/migrate/20250427131736_create_active_storage_variant_records.active_storage.rb
  46. +8
    -0
      db/migrate/20250427131737_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
  47. +1
    -1
      db/schema.rb
  48. +1
    -1
      db/seeds.rb
  49. +58
    -0
      public/400.html
  50. +5
    -2
      public/404.html
  51. +54
    -0
      public/406-unsupported-browser.html
  52. +4
    -2
      public/422.html
  53. +4
    -2
      public/500.html
  54. +0
    -0
      restart.sh
  55. +2
    -0
      vendor/javascript/@hotwired--stimulus.js
  56. +3
    -1
      vendor/javascript/@rails--activestorage.js
  57. +3
    -1
      vendor/javascript/@rails--request.js.js
  58. +7
    -5
      vendor/javascript/sortablejs.js
  59. +3
    -1
      vendor/javascript/stimulus-use.js
  60. +4
    -0
      vendor/javascript/tom-select--dist--js--tom-select.base.min.js.js
  61. +0
    -234
      vendor/javascript/tom-select.js
  62. +4
    -1
      vendor/javascript/trix.js

BIN
.DS_Store View File


+ 0
- 1
.ruby-version View File

@ -1 +0,0 @@
ruby-3.3.0

+ 1
- 0
.tool-versions View File

@ -0,0 +1 @@
ruby 3.4.1

+ 2
- 2
Gemfile View File

@ -1,11 +1,11 @@
source "https://rubygems.org"
ruby "3.3.0"
ruby "3.4.1"
gem 'dotenv-rails'
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3", ">= 7.1.3.2"
gem "rails", "~> 8.0.2"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
# gem "sprockets-rails"


+ 146
- 137
Gemfile.lock View File

@ -1,81 +1,78 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.4)
actionpack (= 7.1.4)
activesupport (= 7.1.4)
actioncable (8.0.2)
actionpack (= 8.0.2)
activesupport (= 8.0.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.4)
actionpack (= 7.1.4)
activejob (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.4)
actionpack (= 7.1.4)
actionview (= 7.1.4)
activejob (= 7.1.4)
activesupport (= 7.1.4)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
actionmailbox (8.0.2)
actionpack (= 8.0.2)
activejob (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
mail (>= 2.8.0)
actionmailer (8.0.2)
actionpack (= 8.0.2)
actionview (= 8.0.2)
activejob (= 8.0.2)
activesupport (= 8.0.2)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.1.4)
actionview (= 7.1.4)
activesupport (= 7.1.4)
actionpack (8.0.2)
actionview (= 8.0.2)
activesupport (= 8.0.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.4)
actionpack (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
useragent (~> 0.16)
actiontext (8.0.2)
actionpack (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.4)
activesupport (= 7.1.4)
actionview (8.0.2)
activesupport (= 8.0.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.1.4)
activesupport (= 7.1.4)
activejob (8.0.2)
activesupport (= 8.0.2)
globalid (>= 0.3.6)
activemodel (7.1.4)
activesupport (= 7.1.4)
activerecord (7.1.4)
activemodel (= 7.1.4)
activesupport (= 7.1.4)
activemodel (8.0.2)
activesupport (= 8.0.2)
activerecord (8.0.2)
activemodel (= 8.0.2)
activesupport (= 8.0.2)
timeout (>= 0.4.0)
activestorage (7.1.4)
actionpack (= 7.1.4)
activejob (= 7.1.4)
activerecord (= 7.1.4)
activesupport (= 7.1.4)
activestorage (8.0.2)
actionpack (= 8.0.2)
activejob (= 8.0.2)
activerecord (= 8.0.2)
activesupport (= 8.0.2)
marcel (~> 1.0)
activesupport (7.1.4)
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
acts_as_list (1.2.2)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
acts_as_list (1.2.4)
activerecord (>= 6.1)
activesupport (>= 6.1)
addressable (2.8.7)
@ -84,52 +81,55 @@ GEM
activerecord (>= 5.2.6)
base64 (0.2.0)
bcrypt (3.1.20)
bigdecimal (3.1.8)
benchmark (0.4.0)
bigdecimal (3.1.9)
bindex (0.8.1)
bootsnap (1.18.4)
msgpack (~> 1.2)
builder (3.3.0)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
concurrent-ruby (1.3.5)
connection_pool (2.5.2)
crass (1.0.6)
css_parser (1.19.0)
css_parser (1.21.1)
addressable
date (3.3.4)
date (3.4.1)
date_validator (0.12.0)
activemodel (>= 3)
activesupport (>= 3)
dkim (1.1.0)
dotenv (3.1.2)
dotenv-rails (3.1.2)
dotenv (= 3.1.2)
dotenv (3.1.8)
dotenv-rails (3.1.8)
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.1)
erubi (1.13.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-arm-linux-gnu)
ffi (1.17.0-arm64-darwin)
ffi (1.17.0-x86-linux-gnu)
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x86_64-linux-gnu)
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)
globalid (1.2.1)
activesupport (>= 6.1)
htmlentities (4.3.4)
i18n (1.14.6)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.0.1)
importmap-rails (2.1.0)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.7.2)
irb (1.14.0)
io-console (0.8.0)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.13.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.11.3)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
@ -142,8 +142,8 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
logger (1.6.1)
loofah (2.22.0)
logger (1.7.0)
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@ -152,40 +152,45 @@ GEM
net-pop
net-smtp
marcel (1.0.4)
mini_magick (4.13.2)
mini_magick (5.2.0)
benchmark
logger
mini_mime (1.1.5)
minitest (5.25.1)
mobility (1.3.0.rc3)
mini_portile2 (2.8.8)
minitest (5.25.5)
mobility (1.3.2)
i18n (>= 0.6.10, < 2)
request_store (~> 1.0)
msgpack (1.7.2)
mutex_m (0.2.0)
net-imap (0.4.16)
msgpack (1.8.0)
net-imap (0.5.7)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-smtp (0.5.1)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7-aarch64-linux)
nio4r (2.7.4)
nokogiri (1.18.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.7-arm-linux)
nokogiri (1.18.8-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.16.7-arm64-darwin)
nokogiri (1.18.8-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.16.7-x86-linux)
nokogiri (1.18.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
nokogiri (1.18.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4)
pg (1.5.8)
pg (1.5.9)
pg_search (2.3.7)
activerecord (>= 6.1)
activesupport (>= 6.1)
pp (0.6.2)
prettyprint
premailer (1.27.0)
addressable
css_parser (>= 1.19.0)
@ -194,95 +199,99 @@ GEM
actionmailer (>= 3)
net-smtp
premailer (~> 1.7, >= 1.7.9)
propshaft (1.0.0)
prettyprint (0.2.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.1.2)
psych (5.2.3)
date
stringio
public_suffix (6.0.1)
puma (6.4.2)
puma (6.6.0)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.7)
rack-session (2.0.0)
rack (3.1.13)
rack-session (2.1.0)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.1.0)
rack-test (2.2.0)
rack (>= 1.3)
rackup (2.1.0)
rackup (2.2.1)
rack (>= 3)
webrick (~> 1.8)
rails (7.1.4)
actioncable (= 7.1.4)
actionmailbox (= 7.1.4)
actionmailer (= 7.1.4)
actionpack (= 7.1.4)
actiontext (= 7.1.4)
actionview (= 7.1.4)
activejob (= 7.1.4)
activemodel (= 7.1.4)
activerecord (= 7.1.4)
activestorage (= 7.1.4)
activesupport (= 7.1.4)
rails (8.0.2)
actioncable (= 8.0.2)
actionmailbox (= 8.0.2)
actionmailer (= 8.0.2)
actionpack (= 8.0.2)
actiontext (= 8.0.2)
actionview (= 8.0.2)
activejob (= 8.0.2)
activemodel (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
bundler (>= 1.15.0)
railties (= 7.1.4)
railties (= 8.0.2)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.1.4)
actionpack (= 7.1.4)
activesupport (= 7.1.4)
irb
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.2)
actionpack (= 8.0.2)
activesupport (= 8.0.2)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rake (13.2.1)
rdoc (6.7.0)
rdoc (6.13.1)
psych (>= 4.0.0)
redis (5.3.0)
redis (5.4.0)
redis-client (>= 0.22.0)
redis-client (0.22.2)
redis-client (0.24.0)
connection_pool
reline (0.5.10)
reline (0.6.1)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
ruby-vips (2.2.2)
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
sidekiq (7.3.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
logger
rack (>= 2.2.4)
redis-client (>= 0.22.2)
securerandom (0.4.1)
sidekiq (8.0.3)
connection_pool (>= 2.5.0)
json (>= 2.9.0)
logger (>= 1.6.2)
rack (>= 3.1.0)
redis-client (>= 0.23.2)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.1)
stringio (3.1.7)
thor (1.3.2)
timeout (0.4.1)
turbo-rails (2.0.7)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
timeout (0.4.3)
turbo-rails (2.0.13)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uri (1.0.3)
useragent (0.16.11)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webrick (1.8.1)
websocket-driver (0.7.6)
websocket-driver (0.7.7)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
zeitwerk (2.6.18)
zeitwerk (2.7.2)
PLATFORMS
aarch64-linux
@ -310,7 +319,7 @@ DEPENDENCIES
premailer-rails
propshaft
puma (>= 5.0)
rails (~> 7.1.3, >= 7.1.3.2)
rails (~> 8.0.2)
redis (>= 4.0.1)
sidekiq
stimulus-rails
@ -318,7 +327,7 @@ DEPENDENCIES
web-console
RUBY VERSION
ruby 3.3.0p0
ruby 3.4.1p0
BUNDLED WITH
2.5.17

+ 11
- 11
README DB.txt View File

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

+ 1
- 2
README.txt View File

@ -16,8 +16,7 @@ EDITOR=vi rails credentials:edit
SSL
-------------------
sudo certbot certonly --webroot -w /home/ikea_foundation/week_2024/public -d ikea-foundation-week-2024.onc.dk
sudo certbot certonly --webroot -w /home/ikea_foundation/week_2024/public -d spotlinks.ikeafoundation.org
sudo certbot certonly --webroot -w /home/ikea_foundation/week_2025/public -d ikea-foundation-week-2025.onc.dk
Gem


+ 0
- 0
app/assets/fonts/NotoIKEALatin-Bold.ttf View File


+ 0
- 0
app/assets/fonts/NotoIKEALatin-BoldItalic.ttf View File


+ 0
- 0
app/assets/fonts/NotoIKEALatin-Italic.ttf View File


+ 0
- 0
app/assets/fonts/NotoIKEALatin-Regular.ttf View File


+ 0
- 0
app/assets/images/windmill.png View File

Before After
Width: 630  |  Height: 963  |  Size: 196 KiB Width: 630  |  Height: 963  |  Size: 196 KiB

+ 15
- 1
app/assets/stylesheets/assets.css View File

@ -74,10 +74,24 @@
position: relative;
padding: 10px 10px 40px 10px;
border: 2px solid transparent;
border-radius: 10px;
&.loading {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
border-color: var(--clr-grey-200);
& .progress::after {
content: ' %';
}
}
&:hover {
border-color: var(--clr-grey-200);
border-radius: 10px;
& .asset-ctrls {
bottom: 6px;
position: absolute;


+ 2
- 3
app/controllers/admin/assets_controller.rb View File

@ -11,7 +11,7 @@ class Admin::AssetsController < Admin::AdminController
.simple_search(params[:q])
.page(params[:page] || 1)
render (params[:node_id] || params[:newsletter_id]) ? 'explore' : 'index'
render (params[:screen_id] || params[:node_id]) ? 'explore' : 'index'
end
@ -75,8 +75,7 @@ private
# Only allow a list of trusted parameters through.
def asset_params
params.require(:asset).permit(
:title,
tags: []
:title
)
end


+ 0
- 230
app/javascript/application.js View File

@ -1,230 +0,0 @@
import "@hotwired/turbo-rails"
var input_range
const max_value = 180
const speed = 12
var curr_value
var raf_id
var random_salt = Math.random()
function rand_in_range(from, to) {
const rand = Math.floor(Math.random() * (to - from + 1)) + from
const is_neg = Math.random() < 0.5
return is_neg ? -rand : rand
}
function shuffle_cards() {
const cards = document.querySelectorAll('.card__container')
const card_ids = Array.from(cards).map(card => card.getAttribute('data-id'))
let shown_cards = JSON.parse(localStorage.getItem('shown_cards')) || [];
let cards_not_shown = card_ids.filter(id => !shown_cards.includes(id));
if (cards_not_shown.length <= 0) {
cards_not_shown = card_ids
shown_cards = []
}
const random_id = cards_not_shown[Math.floor(Math.random() * cards_not_shown.length)];
cards.forEach((card) => {
if (card.getAttribute('data-id') == random_id) {
card.style.zIndex = '1'
card.style.transform = ''
card.classList.add('active')
} else {
card.classList.remove('active')
card.style.zIndex = 'auto'
card.style.transform = `rotate(${rand_in_range(2,5)}deg) translate(${rand_in_range(2,5)}px, ${rand_in_range(2,5)}px)`
}
})
shown_cards.push(random_id);
localStorage.setItem('shown_cards', JSON.stringify(shown_cards));
}
document.addEventListener("turbo:load", ()=> {
init()
})
document.addEventListener("turbo:visit", (event) => {
plausible('pageview', { u: event.detail.url });
})
document.addEventListener("turbo:before-cache", () => {
if (input_range) {
input_range.value = 0;
input_range.closest('.reveal__container').style.setProperty('--opacity', 1);
updateRotation(input_range.value)
}
let images = document.querySelectorAll("img.fade-in.loaded")
images.forEach(image => {
image.classList.remove("loaded")
})
})
function init() {
random_salt = Math.random()
document.querySelectorAll('.button_shuffle_cards').forEach((item, index) => {
item.addEventListener('click', (e) => {
shuffle_cards()
})
if (index == 0)
shuffle_cards()
})
document.querySelectorAll('#language_select').forEach((language_select) => {
language_select.addEventListener('change', (e) => {
document.documentElement.lang = language_select.value
})
})
// Choose language confirmation button
document.querySelectorAll('#confirm_btn').forEach((confirm_btn) => {
confirm_btn.addEventListener('click', (e) => {
// window.location.href = '/' + document.getElementById('language_select').value
Turbo.visit('/' + document.getElementById('language_select').value, {advance: 'advance'})
})
})
// Copy link to clipboard
document.querySelectorAll('[data-share-title]').forEach((share_btn) => {
share_btn.addEventListener('click', (e) => {
if (navigator.share) {
navigator.share({
title: e.currentTarget.getAttribute('data-share-title'),
text: e.currentTarget.getAttribute('data-share-text'),
url: window.location.href
}).then(() => {
console.log('Thanks for sharing!');
})
.catch(console.error);
} else {
navigator.clipboard.writeText(window.location.href)
alert('URL copied to clipboard!')
}
})
})
document.querySelectorAll('.card__stack a').forEach((card_link) => {
card_link.addEventListener('click', (e) => {
e.preventDefault()
})
})
// Open all external links in ny tab
for (const link of document.links) {
if (link.hostname.replace(/^www\./i, "") != window.location.hostname.replace(/^www\./i, "") && (link.protocol == 'https:' || link.protocol == 'http:'))
link.target = '_blank'
}
input_range = document.getElementsByClassName('reveal')[0]
if (input_range) {
input_range.min = 0;
input_range.max = max_value;
}
if (input_range) {
input_range.addEventListener('mousedown', unlockStartHandler, false)
input_range.addEventListener('touchstart', unlockStartHandler, false)
input_range.addEventListener('mouseup', unlockEndHandler, false)
input_range.addEventListener('touchend', unlockEndHandler, false)
input_range.addEventListener("input", (e) => {
e.currentTarget.closest('.reveal__container').style.setProperty('--opacity', 1 - (e.currentTarget.value/e.currentTarget.max))
updateRotation(e.currentTarget.value)
})
}
let images = document.querySelectorAll("img.fade-in")
images.forEach(image => {
image.addEventListener("load", () => {
image.classList.add("loaded")
})
// For cached images that may already be loaded
if (image.complete) {
window.requestAnimationFrame(() => {
image.classList.add("loaded")
})
}
})
}
function unlockStartHandler() {
window.cancelAnimationFrame(raf_id);
curr_value = +this.value;
}
function unlockEndHandler() {
curr_value = +this.value;
if (curr_value >= this.max) {
const card = document.querySelector('.card__container.active')
//window.location.href = card.parentNode.getAttribute('href')
Turbo.visit(card.parentNode.getAttribute('href'), { action: 'advance'} )
} else {
raf_id = window.requestAnimationFrame(animateHandler);
}
}
// handle range animation
function animateHandler() {
input_range.value = curr_value;
input_range.closest('.reveal__container').style.setProperty('--opacity', 1 - (input_range.value/input_range.max));
updateRotation(input_range.value)
// continue?
if (curr_value > -1) {
window.requestAnimationFrame(animateHandler);
}
// decrement
curr_value = curr_value - speed;
}
function updateRotation(angle) {
document.documentElement.style.setProperty('--flip-deg', angle + 'deg')
document.documentElement.style.setProperty('--flip-scale', calculateScaleFactor(angle))
document.documentElement.style.setProperty('--flip-rotate', calculateRotateFactor(angle) + 'deg')
}
function calculateScaleFactor(angle) {
const radians = angle * (Math.PI / 180)
return 1 + (0.1 * Math.sin(radians))
// return 1 + (0.2 * (angle / 180))
}
function calculateRotateFactor(angle) {
const radians = angle * (Math.PI / 180)
return (4 * Math.sin(radians)) + (random_salt * 2)
}

+ 10
- 8
app/javascript/controllers/upload_controller.js View File

@ -27,15 +27,17 @@ export default class extends Controller {
const uid = 'new_' + Date.now() + '_' + index
const target_div = document.createElement('div')
target_div.setAttribute('id', uid)
target_div.classList.add('asset', 'loading')
target_div.innerHTML = '<span class="progress">0</span>'
if (this.targetValue == 'attachments') {
target_div.classList.add('attachment', 'loading')
target_div.innerHTML = '<span class="progress">0</span>'
} else if (this.targetValue == 'tree-nodes') {
target_div.classList.add('row')
target_div.innerHTML = '<div class="cell"><span class="node-link progress" data-icon="description">0</span></div>'
}
container.append(target_div)
// if (this.targetValue == 'attachments') {
// target_div.classList.add('attachment', 'loading')
// target_div.innerHTML = '<span class="progress">0</span>'
// } else if (this.targetValue == 'tree-nodes') {
// target_div.classList.add('row')
// target_div.innerHTML = '<div class="cell"><span class="node-link progress" data-icon="description">0</span></div>'
// }
container.prepend(target_div)
new Uploader(this, file, uid).start()
});


+ 2
- 2
app/models/node.rb View File

@ -26,9 +26,9 @@ class Node < ApplicationRecord
NODE_TEMPLATES = %w"tmpl_article tmpl_index tmpl_list"
enum status: { status_published: 0, status_draft: 1, status_archived: 2 }
enum :status, { status_published: 0, status_draft: 1, status_archived: 2 }
enum template: { tmpl_index: 0,
enum :template, { tmpl_index: 0,
tmpl_article: 1,
tmpl_list: 2
}


+ 1
- 1
app/models/user.rb View File

@ -1,6 +1,6 @@
class User < ApplicationRecord
enum role: { user: 'user', admin: 'admin' }, _suffix: true
enum :role, { user: 'user', admin: 'admin' }, suffix: true
include PgSearch::Model
pg_search_scope :pg_search, against: [:lastname, :firstname, :email, :phone, :title],


+ 3
- 4
app/views/admin/assets/_asset.html.erb View File

@ -1,7 +1,7 @@
<%= tag.div class: 'asset', id: dom_id(asset) do %>
<div class="asset__thumbnail">
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320])) if asset.file.representable? %></div>
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %></div>
</div>
<%= tag.div asset.title, class: 'asset__title' %>
@ -23,11 +23,10 @@
<div class="asset-ctrls">
<div>
<%= link_to 'edit', edit_admin_asset_path(asset), data: {turbo_frame: 'main', turbo_action: 'advance'}, title: t('ui.edit') %>
<%= link_to 'download', rails_storage_proxy_path(asset.file, disposition: "attachment", locale: nil), title: t('ui.download') %>
<%= link_to 'download', rails_blob_path(asset.file, disposition: "attachment"), title: t('ui.download') %>
</div>
<%= link_to 'delete_forever', admin_asset_path(asset), data: { turbo_confirm: t(:'ui.are_you_sure'), turbo_method: :delete }, title: t('ui.delete') %>
</div>
<% end %>

+ 0
- 10
app/views/admin/assets/_form.html.erb View File

@ -1,19 +1,9 @@
<%= form_with(model: [ :admin, asset ], class: 'form-plain has--key-ctrls') do |form| %>
<div class="form-section">
<%= render partial: 'material/text_field', locals: { f: form, attr: :title } %>
<%= render partial: 'material/tom_select_field',
locals: {
f: form,
attr: :tags,
choices: [Asset::STICKER],
multiple: true
} %>
</div>
<div class="form-ctrls">
<%= link_to t(:'ui.cancel'), url_for(controller: 'assets', action: 'index'), data: {turbo_action: 'advance'} %>
<%= form.submit t(:'ui.save') %>


+ 1
- 1
app/views/admin/assets/_list.html.erb View File

@ -2,7 +2,7 @@
<h1><%= link_to yield(:title), params.permit(:sort, :reverse), class: 'list-title-link' %></h1>
<div class="flex">
<%= render partial: 'material/search', locals: (params[:node_id] ? {turbo_stream: true} : nil) %>
<%= render partial: 'material/search', locals: (params[:screen_id] ? {turbo_stream: true} : nil) %>
<%= tag.form do %>
<%= tag.label class: "dropzone dropzone--small",


+ 2
- 3
app/views/admin/assets/explore.turbo_stream.erb View File

@ -4,7 +4,7 @@
<turbo-stream action="append" targets="body">
<template>
<div id="overlay" class="overlay" data-controller="utils assets" data-action="click->utils#closeOverlay" data-assets-url-value="<%= url_for(controller: 'attachments', action: 'new', node_id: params[:node_id] ) %>">
<div id="overlay" class="overlay" data-controller="utils assets" data-action="click->utils#closeOverlay" data-assets-url-value="<%= url_for(controller: 'attachments', action: 'new', screen_id: params[:screen_id] ) %>">
<div class="inner-window-container">
<%= button_tag 'close', class: 'close-overlay', data: { action: "click->utils#closeOverlay" } %>
@ -14,8 +14,7 @@
<div class="form-ctrls form-ctrls-overlay">
<%= button_tag t(:'ui.append'), data: { action: "click->assets#appendAttachments" } %>
</div>
</div>
</div>
</div>
</div>


+ 3
- 7
app/views/admin/assets/upload.turbo_stream.erb View File

@ -1,7 +1,3 @@
<turbo-stream action="prepend" targets="#assets">
<template>
<%= render partial: 'asset', object: @asset, formats: :html %>
</template>
</turbo-stream>
<%= turbo_stream.replace params[:uid] do %>
<%= render partial: 'asset', object: @asset, formats: :html %>
<% end %>

+ 1
- 21
app/views/admin/attachments/_attachment.html.erb View File

@ -12,27 +12,7 @@
} %>
<!--<div class="attachment__content-three">
<%= render partial: 'material/text_field', locals: { f: f, attr: :bg_color } %>
<%= render partial: 'material/text_field', locals: { f: f, attr: :fg_color } %>
<%= render partial: 'material/select_field',
locals: {
f: f,
attr: :alignment,
choices: (f.object.attachable_for&.site? ? Attachment::ALIGNMENT_SITE : Attachment::ALIGNMENT_TILE).map { |k,v| [t(k, scope: :'attachments.alignments'), k] },
include_blank: true
} %>
<%= render partial: 'material/select_field',
locals: {
f: f,
attr: :template,
choices: Array(Attachment::TEMPLATE_SITE_ASSET).map { |k,v| [t(k, scope: :'attachments.templates'), k] },
include_blank: true
} %>
</div>-->
</div>


+ 2
- 4
app/views/layouts/application.html.erb View File

@ -12,9 +12,7 @@
<%= stylesheet_link_tag "application" %>
<%= frontend_javascript_importmap_tags %>
<script defer data-domain="spotlinks.ikeafoundation.org" src="https://plausible.io/js/script.outbound-links.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
</head>
<body>
<header>
@ -34,6 +32,6 @@
<%= yield %>
</main>
<footer>© Inter IKEA Systems B.V. 2024</footer>
<footer>© Inter IKEA Systems B.V. 2025</footer>
</body>
</html>

+ 7
- 0
bin/brakeman View File

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
ARGV.unshift("--ensure-latest")
load Gem.bin_path("brakeman", "brakeman")

+ 2
- 0
bin/dev View File

@ -0,0 +1,2 @@
#!/usr/bin/env ruby
exec "./bin/rails", "server", *ARGV

+ 8
- 0
bin/rubocop View File

@ -0,0 +1,8 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
# explicit rubocop config increases performance slightly while avoiding config confusion.
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
load Gem.bin_path("rubocop", "rubocop")

+ 5
- 4
bin/setup View File

@ -1,7 +1,6 @@
#!/usr/bin/env ruby
require "fileutils"
# path to your application root.
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
@ -14,7 +13,6 @@ FileUtils.chdir APP_ROOT do
# Add necessary setup steps to this file.
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
@ -28,6 +26,9 @@ FileUtils.chdir APP_ROOT do
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
system! "bin/rails restart"
unless ARGV.include?("--skip-server")
puts "\n== Starting development server =="
STDOUT.flush # flush the output before exec(2) so that it displays
exec "bin/dev"
end
end

+ 5
- 3
config/application.rb View File

@ -18,14 +18,16 @@ require "rails/test_unit/railtie"
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Week2024
module Week2025
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.1
config.load_defaults 8.0
# 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.
# Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w[assets tasks])
config.autoload_lib(ignore: %w(assets tasks))
config.time_zone = "Copenhagen"
@ -36,13 +38,13 @@ module Week2024
config.active_storage.routes_prefix = 'storage' #change /rails/active_storage to /storage
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
config.time_zone = "Copenhagen"
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end

+ 10
- 0
config/cable.yml View File

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

+ 21
- 28
config/environments/development.rb View File

@ -3,9 +3,7 @@ require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded any time
# it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
# Make code changes take effect immediately without server restart.
config.enable_reloading = true
# Do not eager load code on boot.
@ -14,31 +12,31 @@ Rails.application.configure do
# Show full error reports.
config.consider_all_requests_local = true
# Enable server timing
# Enable server timing.
config.server_timing = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
# Enable/disable Action Controller caching. By default Action Controller caching is disabled.
# Run rails dev:cache to toggle Action Controller caching.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=#{2.days.to_i}"
}
config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Change to :null_store to avoid any caching.
config.cache_store = :memory_store
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
# Make template changes take effect immediately.
config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { host: 'gw.oncotype.dk', protocol: 'https' }
@ -46,43 +44,38 @@ Rails.application.configure do
config.action_mailer.smtp_settings = {
address: 'asmtp.bluepipe.dk',
port: 587,
domain: 'week2024.ikeafoundation.org',
domain: 'ikea-foundation-week-2025.onc.dk',
authentication: :login,
user_name: 'oncotype@oncotype.dk',
password: 'y9usn&rpErwY'
}
config.hosts << "gw.oncotype.dk"
config.action_mailer.perform_caching = false
# config.hosts << "gw.oncotype.dk"
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Append comments with runtime information tags to SQL queries in logs.
config.active_record.query_log_tags_enabled = true
# Highlight code that enqueued background job in logs.
config.active_job.verbose_enqueue_logs = true
# Suppress logger output for asset requests.
config.assets.quiet = true
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
config.action_view.annotate_rendered_view_with_filenames = true
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
# Raise error when a before_action's only/except options reference missing actions
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end

+ 36
- 42
config/environments/production.rb View File

@ -6,67 +6,51 @@ Rails.application.configure do
# Code is not reloaded between requests.
config.enable_reloading = false
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
config.eager_load = true
# Full error reports are disabled and caching is turned on.
# Full error reports are disabled.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
# key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
# config.public_file_server.enabled = false
# Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Turn on fragment caching in view templates.
config.action_controller.perform_caching = true
# Do not fall back to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Cache assets for far-future expiry since they are all digest stamped.
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
# config.assume_ssl = true
config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false
config.force_ssl = true
# Log to STDOUT by default
config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = ::Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# Skip http-to-https redirect for the default health check endpoint.
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
# Prepend all log lines with the following tags.
# Log to STDOUT with the current request id as a default log tag.
config.log_tags = [ :request_id ]
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
# "info" includes generic and useful information about system operation, but avoids logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII). If you
# want to log everything, set the level to "debug".
# Change to "debug" to log everything (including potentially personally-identifiable information!)
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
# Use a different cache store in production.
# Prevent health checks from clogging up the logs.
config.silence_healthcheck_path = "/up"
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Replace the default in-process memory cache store with a durable alternative.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# Replace the default in-process and non-durable queuing backend for Active Job.
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "week_2024_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
@ -74,30 +58,40 @@ Rails.application.configure do
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { host: 'spotlinks.ikeafoundation.org', protocol: 'https' }
# Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: 'ikea-foundation-week-2025.onc.dk', protocol: 'https' }
config.action_mailer.smtp_settings = {
address: 'smtp.bluepipe.net',
port: 25,
domain: 'spotlinks.ikeafoundation.org'
domain: 'ikea-foundation-week-2025.onc.dk'
}
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
# config.action_mailer.smtp_settings = {
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
# password: Rails.application.credentials.dig(:smtp, :password),
# address: "smtp.example.com",
# port: 587,
# authentication: :plain
# }
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Only use :id for inspections in production.
config.active_record.attributes_for_inspect = [ :id ]
# Enable DNS rebinding protection and other `Host` header attacks.
# config.hosts = [
# "example.com", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
# ]
#
# Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end

+ 7
- 18
config/environments/test.rb View File

@ -1,5 +1,3 @@
require "active_support/core_ext/integer/time"
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
@ -17,15 +15,11 @@ Rails.application.configure do
# loading is working properly before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
}
# Configure public file server for tests with cache-control for performance.
config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }
# Show full error reports and disable caching.
# Show full error reports.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Render exception templates for rescuable exceptions and raise for other exceptions.
@ -37,28 +31,23 @@ Rails.application.configure do
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "example.com" }
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Raise error when a before_action's only/except options reference missing actions
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end

+ 6
- 6
config/importmap.rb View File

@ -5,12 +5,12 @@ pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin "sortablejs" # @1.15.2
pin "stimulus-use" # @0.52.2
pin "@rails/request.js", to: "@rails--request.js.js" # @0.0.9
pin "@rails/activestorage", to: "@rails--activestorage.js" # @7.1.3
pin "tom-select" # @2.3.1
pin "trix" # @2.1.3
pin "sortablejs" # @1.15.6
pin "stimulus-use" # @0.52.3
pin "@rails/request.js", to: "@rails--request.js.js" # @0.0.11
pin "@rails/activestorage", to: "@rails--activestorage.js" # @8.0.200
pin "tom-select", to: "tom-select--dist--js--tom-select.base.min.js.js" # @2.4.3
pin "trix" # @2.1.13
# site_helper
pin "application", preload: false

+ 0
- 5
config/initializers/assets.rb View File

@ -5,8 +5,3 @@ Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )

+ 1
- 1
config/initializers/filter_parameter_logging.rb View File

@ -4,5 +4,5 @@
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
]

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

@ -0,0 +1,70 @@
# 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

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

@ -0,0 +1,30 @@
# 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

+ 1
- 1
config/initializers/premailer_rails.rb View File

@ -4,5 +4,5 @@ Premailer::Rails.config.merge!(
input_encoding: 'UTF-8',
generate_text_part: true,
strategies: [:filesystem, :asset_pipeline, :network],
base_url: (Rails.env.production? ? 'https://week2024.ikeafoundation.org' : 'https://gw.oncotype.dk')
base_url: (Rails.env.production? ? 'ikea-foundation-week-2025.onc.dk' : 'https://gw.oncotype.dk')
)

+ 2
- 2
config/locales/en.yml View File

@ -1,6 +1,6 @@
en:
domain: week2024.ikeafoundation.org
project_name: IKEA Foundation Week 2024
domain: ikea-foundation-week-2025.onc.dk
project_name: IKEA Foundation Week 2025
client_name: IKEA Foundation
can_you_spot_the_link: Can you spot <span>the link?</span>


+ 52
- 48
config/nginx.conf View File

@ -1,9 +1,9 @@
# http > https
server {
listen 80;
server_name ikea-foundation-week-2024.onc.dk;
server_name ikea-foundation-week-2025.onc.dk;
root /home/ikea_foundation/week_2024/public;
root /home/ikea_foundation/week_2025/public;
# Lets encrypt
location ~ /.well-known {
@ -11,51 +11,51 @@ server {
}
location / {
return 301 https://ikea-foundation-week-2024.onc.dk$request_uri;
return 301 https://ikea-foundation-week-2025.onc.dk$request_uri;
}
}
# https
server {
listen 443 ssl;
http2 on;
server_name ikea-foundation-week-2024.onc.dk;
# keepalive_timeout 300;
keepalive_timeout 5;
client_max_body_size 4G;
ssl_certificate /etc/letsencrypt/live/ikea-foundation-week-2024.onc.dk/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ikea-foundation-week-2024.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://spotlinks.ikeafoundation.org$request_uri;
}
}
# http > https
server {
listen 80;
server_name spotlinks.ikeafoundation.org;
root /home/ikea_foundation/week_2024/public;
# Lets encrypt
location ~ /.well-known {
allow all;
}
location / {
return 301 https://spotlinks.ikeafoundation.org$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://spotlinks.ikeafoundation.org$request_uri;
# }
# }
# # http > https
# server {
# listen 80;
# server_name spotlinks.ikeafoundation.org;
#
# root /home/ikea_foundation/week_2025/public;
#
# # Lets encrypt
# location ~ /.well-known {
# allow all;
# }
#
# location / {
# return 301 https://spotlinks.ikeafoundation.org$request_uri;
# }
# }
# Puma
upstream puma_ikea {
server unix:///home/ikea_foundation/week_2024/tmp/pids/puma.sock fail_timeout=0;
server unix:///home/ikea_foundation/week_2025/tmp/pids/puma.sock fail_timeout=0;
}
@ -65,21 +65,25 @@ server {
listen 443 ssl;
http2 on;
server_name spotlinks.ikeafoundation.org;
# server_name spotlinks.ikeafoundation.org;
server_name ikea-foundation-week-2025.onc.dk;
# keepalive_timeout 300;
keepalive_timeout 5;
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/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/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
access_log /home/ikea_foundation/week_2024/log/httpd-access.log;
error_log /home/ikea_foundation/week_2024/log/httpd-errors.log;
access_log /home/ikea_foundation/week_2025/log/httpd-access.log;
error_log /home/ikea_foundation/week_2025/log/httpd-errors.log;
root /home/ikea_foundation/week_2024/public;
root /home/ikea_foundation/week_2025/public;
# App server
location @puma_ikea {
@ -125,6 +129,6 @@ server {
# application's public folder, if you so desire:
error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/ikea_foundation/week_2024/public;
root /home/ikea_foundation/week_2025/public;
}
}

+ 26
- 21
config/puma.rb View File

@ -1,29 +1,39 @@
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
#
# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You
# should only set this value when you want to run 2 or more workers. The
# default is already 1.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
# Specifies that the worker count should equal the number of processors in production.
if ENV["RAILS_ENV"] == "production"
require "concurrent-ruby"
worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
workers worker_count if worker_count > 1
end
workers 4
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
# worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
# port ENV.fetch("PORT") { 3000 }
@ -39,10 +49,5 @@ shared_dir = "#{app_dir}/tmp/pids"
# Logging
stdout_redirect "#{app_dir}/log/puma_access.log", "#{app_dir}/log/puma_error.log", true
# Set up socket location
bind "unix://#{shared_dir}/puma.sock"
# Allow puma to be restarted by `bin/rails restart` command.
# plugin :tmp_restart

+ 22
- 0
db/migrate/20250427131735_add_service_name_to_active_storage_blobs.active_storage.rb View File

@ -0,0 +1,22 @@
# This migration comes from active_storage (originally 20190112182829)
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
def up
return unless table_exists?(:active_storage_blobs)
unless column_exists?(:active_storage_blobs, :service_name)
add_column :active_storage_blobs, :service_name, :string
if configured_service = ActiveStorage::Blob.service.name
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
end
change_column :active_storage_blobs, :service_name, :string, null: false
end
end
def down
return unless table_exists?(:active_storage_blobs)
remove_column :active_storage_blobs, :service_name
end
end

+ 27
- 0
db/migrate/20250427131736_create_active_storage_variant_records.active_storage.rb View File

@ -0,0 +1,27 @@
# This migration comes from active_storage (originally 20191206030411)
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
def change
return unless table_exists?(:active_storage_blobs)
# Use Active Record's configured type for primary key
create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|
t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
private
def primary_key_type
config = Rails.configuration.generators
config.options[config.orm][:primary_key_type] || :primary_key
end
def blobs_primary_key_type
pkey_name = connection.primary_key(:active_storage_blobs)
pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
pkey_column.bigint? ? :bigint : pkey_column.type
end
end

+ 8
- 0
db/migrate/20250427131737_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb View File

@ -0,0 +1,8 @@
# This migration comes from active_storage (originally 20211119233751)
class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]
def change
return unless table_exists?(:active_storage_blobs)
change_column_null(:active_storage_blobs, :checksum, true)
end
end

+ 1
- 1
db/schema.rb View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_05_09_121618) do
ActiveRecord::Schema[7.2].define(version: 2025_04_27_131737) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"


+ 1
- 1
db/seeds.rb View File

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

+ 58
- 0
public/400.html View File

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<title>The server cannot process the request due to a client error (400 Bad Request)</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<style>
body {
background-color: #F0B902;
color: #000;
text-align: center;
font-family: system-ui, sans-serif;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
body {
min-height: 100vh;
min-height: 100svh;
}
h2 {
margin: 0;
font-size: 400px;
line-height: 1;
position: absolute;
top: -0.4em;
left: -0.08em;
opacity: 0.4;
font-weight: 700;
}
h1 {
margin: 0;
font-size: 2em;
font-weight: 300;
}
p {
margin: 1em 0;
}
</style>
</head>
<body>
<div>
<h2>400</h2>
<h1>The server cannot process the request due to a client error.</h1>
<p>Please check the request and try again. If you’re the application owner check the logs for more information.</p>
</div>
</body>
</html>

+ 5
- 2
public/404.html View File

@ -1,8 +1,11 @@
<!DOCTYPE html>
<html>
<!doctype html>
<html lang="en">
<head>
<title>The page you were looking for doesn't exist (404)</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<style>
body {


+ 54
- 0
public/406-unsupported-browser.html View File

@ -0,0 +1,54 @@
<!doctype html>
<html lang="en">
<head>
<title>Your browser is not supported (406)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body {
background-color: #F0B902;
color: #000;
text-align: center;
font-family: system-ui, sans-serif;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
body {
min-height: 100vh;
min-height: 100svh;
}
h2 {
margin: 0;
font-size: 400px;
line-height: 1;
position: absolute;
top: -0.4em;
left: -0.08em;
opacity: 0.4;
font-weight: 700;
}
h1 {
margin: 0;
font-size: 2em;
font-weight: 300;
}
p {
margin: 1em 0;
}
</style>
</head>
<body>
<div>
<h2>406</h2>
<h1>Your browser is not supported.</h1>
<p>Please upgrade your browser to continue.</p>
</div>
</body>
</html>

+ 4
- 2
public/422.html View File

@ -1,8 +1,10 @@
<!DOCTYPE html>
<html>
<!doctype html>
<html lang="en">
<head>
<title>The change you wanted was rejected (422)</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<style>
body {


+ 4
- 2
public/500.html View File

@ -1,8 +1,10 @@
<!DOCTYPE html>
<html>
<!doctype html>
<html lang="en">
<head>
<title>We're sorry, but something went wrong (500)</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<style>
body {


+ 0
- 0
restart.sh View File


+ 2
- 0
vendor/javascript/@hotwired--stimulus.js
File diff suppressed because it is too large
View File


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


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


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


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


+ 4
- 0
vendor/javascript/tom-select--dist--js--tom-select.base.min.js.js
File diff suppressed because it is too large
View File


+ 0
- 234
vendor/javascript/tom-select.js
File diff suppressed because it is too large
View File


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


Loading…
Cancel
Save