QR codes generation
Some checks failed
CI / scan_ruby (push) Failing after 31s
CI / scan_js (push) Successful in 16s
CI / lint (push) Successful in 16s
CI / test (push) Failing after 45s

This commit is contained in:
sto
2025-11-20 12:01:30 +01:00
parent 3e071f9281
commit 709719b801
16 changed files with 63 additions and 5 deletions

View File

@@ -46,6 +46,7 @@ gem "bootstrap", "~> 5.3.3"
gem "friendly_id", "~> 5.5.0" gem "friendly_id", "~> 5.5.0"
gem "csv" gem "csv"
gem "damerau-levenshtein" gem "damerau-levenshtein"
gem "rqrcode", "~> 3.0"
group :development, :test do group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem

View File

@@ -100,6 +100,7 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
chunky_png (1.4.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.4) connection_pool (2.5.4)
crass (1.0.6) crass (1.0.6)
@@ -291,6 +292,10 @@ GEM
reline (0.6.2) reline (0.6.2)
io-console (~> 0.5) io-console (~> 0.5)
rexml (3.4.4) rexml (3.4.4)
rqrcode (3.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.0.0)
rspec-core (3.13.6) rspec-core (3.13.6)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.5)
@@ -455,6 +460,7 @@ DEPENDENCIES
puma (>= 5.0) puma (>= 5.0)
pundit (~> 2.5) pundit (~> 2.5)
rails (~> 8.0.2) rails (~> 8.0.2)
rqrcode (~> 3.0)
rspec-rails rspec-rails
rubocop-rails-omakase rubocop-rails-omakase
selenium-webdriver selenium-webdriver

View File

@@ -2,7 +2,7 @@ class ContestantsController < ApplicationController
include CompletionsConcern include CompletionsConcern
include ContestantsConcern include ContestantsConcern
before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export ] before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export generate_qrcodes ]
before_action :set_contestant, only: %i[ destroy edit update] before_action :set_contestant, only: %i[ destroy edit update]
before_action :set_completions, only: %i[edit update ] before_action :set_completions, only: %i[edit update ]
skip_before_action :require_authentication, only: %i[ get_public_completion post_public_completion public_completion_updated ] skip_before_action :require_authentication, only: %i[ get_public_completion post_public_completion public_completion_updated ]
@@ -42,7 +42,7 @@ class ContestantsController < ApplicationController
if @contestant.update(contestant_params) if @contestant.update(contestant_params)
update_contestant_categories update_contestant_categories
redirect_to @contest, notice: t("contestants.edit.notice") redirect_to contest_contestants_path(@contest), notice: t("contestants.edit.notice")
else else
render :edit, status: :unprocessable_entity render :edit, status: :unprocessable_entity
end end
@@ -120,6 +120,12 @@ class ContestantsController < ApplicationController
end end
end end
def generate_qrcodes
authorize @contest
@contestants = @contest.contestants.sort_by { |contestant| contestant.name }
end
def get_public_completion def get_public_completion
skip_authorization skip_authorization

View File

@@ -7,6 +7,7 @@
# email :string # email :string
# name :string # name :string
# projected_time :string # projected_time :string
# qrcode :string
# time_seconds :integer # time_seconds :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
@@ -27,6 +28,7 @@ class Contestant < ApplicationRecord
has_and_belongs_to_many :categories has_and_belongs_to_many :categories
before_validation :initialize_time_seconds_if_empty before_validation :initialize_time_seconds_if_empty
before_commit :generate_qrcode, on: :create
validates :name, presence: true validates :name, presence: true
validates :time_seconds, presence: true validates :time_seconds, presence: true
@@ -48,4 +50,17 @@ class Contestant < ApplicationRecord
self.time_seconds = 0 self.time_seconds = 0
end end
end end
def generate_qrcode
host = Rails.application.config.action_controller.default_url_options[:host]
qrcode = RQRCode::QRCode.new("https://#{host}/public/p/#{self.generate_token_for(:token)}")
self.qrcode = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 3,
standalone: true,
use_path: true,
viewbox: true
)
end
end end

View File

@@ -33,7 +33,7 @@ class Offline < ApplicationRecord
belongs_to :contestant, optional: true belongs_to :contestant, optional: true
belongs_to :completion, optional: true belongs_to :completion, optional: true
has_many_attached :images has_many_attached :images, dependent: :destroy
generates_token_for :token generates_token_for :token

View File

@@ -22,7 +22,7 @@ class Puzzle < ApplicationRecord
belongs_to :contest belongs_to :contest
has_many :completions, dependent: :destroy has_many :completions, dependent: :destroy
has_one_attached :image has_one_attached :image, dependent: :destroy
validates :name, presence: true validates :name, presence: true
validates :pieces, presence: true validates :pieces, presence: true

View File

@@ -35,6 +35,10 @@ class ContestPolicy < ApplicationPolicy
owner_or_admin owner_or_admin
end end
def generate_qrcodes?
owner_or_admin
end
def settings_general_edit? def settings_general_edit?
edit? edit?
end end

View File

@@ -0,0 +1,11 @@
.row.mb-4 style="height: calc(100vh - 280px)"
.col.d-flex.flex-column style="height: 100%"
.d-flex.flex-column style="overflow-y: auto"
- for row in 0..((@contestants.length - 1) / 4)
.mt-4.d-flex.flex-row
- for col in 0..3
- if row * 4 + col < @contestants.length
.d-flex.flex-column.ms-5 style="align-items: center"
= @contestants[row * 4 + col].name
.mt-1 style="width: 200px; height: 200px;"
= @contestants[row * 4 + col].qrcode.html_safe

View File

@@ -6,6 +6,8 @@
| + #{t("helpers.buttons.add")} | + #{t("helpers.buttons.add")}
a.ms-2.btn.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px" a.ms-2.btn.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
| #{t("helpers.buttons.import")} | #{t("helpers.buttons.import")}
a.ms-2.btn.btn.btn-primary href=contest_generate_qrcodes_path(@contest) style="margin-top: -3px"
| #{t("helpers.buttons.generate_qrcodes")}
a.ms-2.btn.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px" a.ms-2.btn.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px"
| #{t("helpers.buttons.export")} | #{t("helpers.buttons.export")}

View File

@@ -40,6 +40,8 @@ Rails.application.configure do
# Set localhost to be used by links generated in mailer templates. # Set localhost to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "localhost", port: 3000 } config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
config.action_controller.default_url_options = { host: "localhost" }
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log

View File

@@ -60,6 +60,8 @@ Rails.application.configure do
# Set host to be used by links generated in mailer templates. # Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "example.com" } config.action_mailer.default_url_options = { host: "example.com" }
config.action_controller.default_url_options = { host: "public-scoreboard.org" }
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
# config.action_mailer.smtp_settings = { # config.action_mailer.smtp_settings = {
# user_name: Rails.application.credentials.dig(:smtp, :user_name), # user_name: Rails.application.credentials.dig(:smtp, :user_name),

View File

@@ -258,6 +258,7 @@ en:
edit: "Edit" edit: "Edit"
end: Click here to submit your completion end: Click here to submit your completion
export: Export export: Export
generate_qrcodes: Generate QR codes
import: CSV Import import: CSV Import
open: Open open: Open
refresh: Refresh refresh: Refresh

View File

@@ -229,6 +229,7 @@ fr:
edit: "Modifier" edit: "Modifier"
end: Clique ici pour valider ta complétion du puzzle end: Clique ici pour valider ta complétion du puzzle
export: Exporter export: Exporter
generate_qrcodes: Générer des QR codes
import: Importer un CSV import: Importer un CSV
open: Détails open: Détails
refresh: Rafraîchir refresh: Rafraîchir

View File

@@ -26,6 +26,7 @@ Rails.application.routes.draw do
get "import/:id", to: "contestants#convert_csv" get "import/:id", to: "contestants#convert_csv"
post "import/:id", to: "contestants#finalize_import" post "import/:id", to: "contestants#finalize_import"
get "export", to: "contestants#export" get "export", to: "contestants#export"
get "generate_qrcodes", to: "contestants#generate_qrcodes"
end end
resources :passwords, param: :token resources :passwords, param: :token
resource :session resource :session

View File

@@ -0,0 +1,5 @@
class AddQrcodeToContestant < ActiveRecord::Migration[8.0]
def change
add_column :contestants, :qrcode, :string
end
end

3
db/schema.rb generated
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_11_18_074914) do ActiveRecord::Schema[8.0].define(version: 2025_11_20_100813) do
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 "name", null: false
t.string "record_type", null: false t.string "record_type", null: false
@@ -83,6 +83,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_11_18_074914) do
t.string "display_time" t.string "display_time"
t.integer "time_seconds" t.integer "time_seconds"
t.string "projected_time" t.string "projected_time"
t.string "qrcode"
t.index ["contest_id"], name: "index_contestants_on_contest_id" t.index ["contest_id"], name: "index_contestants_on_contest_id"
end end