Events: kickstart shared scoreboard
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
sto
2026-07-30 08:33:55 +02:00
parent b8a2e4b0b4
commit 4a4c79ad8a
13 changed files with 73 additions and 8 deletions

View File

@@ -0,0 +1,7 @@
module ContestsConcern
extend ActiveSupport::Concern
def active_page(path)
request.path.starts_with?(path) ? "active" : ""
end
end

View File

@@ -1,5 +1,6 @@
class ContestsController < ApplicationController class ContestsController < ApplicationController
include CompletionsConcern include CompletionsConcern
include ContestsConcern
include ContestantsConcern include ContestantsConcern
before_action :set_contest, only: %i[ destroy show ] before_action :set_contest, only: %i[ destroy show ]
@@ -168,10 +169,30 @@ class ContestsController < ApplicationController
end end
def scoreboard def scoreboard
@contest = Contest.find_by(slug: params[:id]) if active_page("/public/event")
unless @contest && @contest.public @event = Event.find_by(slug: params[:id])
skip_authorization unless @event && @event.shared_scoreboard && !@event.contests.where(public: true).empty?
not_found and return skip_authorization
not_found and return
end
if params.key?(:contest_id)
begin
@contest = Contest.find(params[:contest_id])
if @contest.event != @event
@contest = @event.contests.where(public: true).first
end
rescue
@contest = @event.contests.where(public: true).first
end
else
@contest = @event.contests.where(public: true).first
end
else
@contest = Contest.find_by(slug: params[:id])
unless @contest && @contest.public
skip_authorization
not_found and return
end
end end
authorize @contest authorize @contest

View File

@@ -35,7 +35,7 @@ class EventsController < ApplicationController
def show def show
authorize @event authorize @event
@contests = @event.contests @contests = @event.contests.includes([ :contestants, :puzzles ])
@title = @event.name @title = @event.name
end end
@@ -65,11 +65,13 @@ class EventsController < ApplicationController
redirect_to events_path, notice: t("events.destroy.notice") redirect_to events_path, notice: t("events.destroy.notice")
end end
private
def set_event def set_event
@event = Event.find(params[:id]) @event = Event.find(params[:id])
end end
def event_params def event_params
params.expect(event: [ :name, :shared_scoreboard ]) params.expect(event: [ :lang, :name, :shared_scoreboard ])
end end
end end

View File

@@ -6,6 +6,7 @@
# lang :string default("en") # lang :string default("en")
# name :string # name :string
# shared_scoreboard :boolean # shared_scoreboard :boolean
# slug :string
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# user_id :integer not null # user_id :integer not null
@@ -19,8 +20,13 @@
# user_id (user_id => users.id) # user_id (user_id => users.id)
# #
class Event < ApplicationRecord class Event < ApplicationRecord
extend FriendlyId
belongs_to :user belongs_to :user
has_many :contests, dependent: :destroy has_many :contests, dependent: :destroy
friendly_id :name, use: :slugged
validates :name, presence: true validates :name, presence: true
validates :lang, inclusion: { in: Languages::AVAILABLE_LANGUAGES.map { |lang| lang[:id] } }
end end

View File

@@ -15,6 +15,13 @@
.alert.alert-primary role="alert" .alert.alert-primary role="alert"
= t("events.form.shared_scoreboard_message") = t("events.form.shared_scoreboard_message")
if method == :patch
.row.mb-3
.col
.form-floating
= form.select :lang, Languages::AVAILABLE_LANGUAGES.map { |lang| [ lang[:name], lang[:id] ] }, {}, class: "form-select"
= form.label :lang
.row.mt-4 .row.mt-4
.col .col
- if method == :patch - if method == :patch

View File

@@ -8,7 +8,14 @@
a.mt-3.btn.btn-primary.mb-4 href=new_contest_path(event: @event.id) a.mt-3.btn.btn-primary.mb-4 href=new_contest_path(event: @event.id)
= t("events.show.new_contest") = t("events.show.new_contest")
.row.row-cols-1.row-cols-md-3.g-4 - if @event.shared_scoreboard
a.btn.btn-success href="/public/event/#{@event.slug}" style="margin-top: -6px;"
= t("events.show.open_shared_scoreboard")
- else
a.btn.btn-success.disabled style="margin-top: -6px;"
= t("events.show.shared_scoreboard_disabled")
.mt-3.row.row-cols-1.row-cols-md-3.g-4
- @contests.each do |contest| - @contests.each do |contest|
.col .col
css: css:

View File

@@ -83,6 +83,7 @@ en:
file: File file: File
separator: Separator separator: Separator
event: event:
lang: Language for th shared scoreboard
name: Name name: Name
shared_scoreboard: Shared scoreboard shared_scoreboard: Shared scoreboard
shared_scoreboard_description: If activated, there will be a scoreboard link specific to the event, with tabs for each contest shared_scoreboard_description: If activated, there will be a scoreboard link specific to the event, with tabs for each contest
@@ -310,6 +311,8 @@ en:
manage_contests: Contests in this event manage_contests: Contests in this event
modify: Settings modify: Settings
new_contest: Add a contest to this event new_contest: Add a contest to this event
shared_scoreboard_disabled: The shared scoreboard is disabled
open_shared_scoreboard: Open shared scoreboard
helpers: helpers:
badges: badges:
registration: "registration" registration: "registration"

View File

@@ -54,6 +54,7 @@ fr:
file: Fichier file: Fichier
separator: Délimiteur separator: Délimiteur
event: event:
lang: Langue pour le classement partagé
name: Nom name: Nom
shared_scoreboard: Classement partagé shared_scoreboard: Classement partagé
shared_scoreboard_description: Si activé, un lien de classement partagé sera disponible pour l'événement, avec des onglets pour chacune des épreuves. shared_scoreboard_description: Si activé, un lien de classement partagé sera disponible pour l'événement, avec des onglets pour chacune des épreuves.
@@ -281,6 +282,8 @@ fr:
manage_contests: Épreuves de cet événement manage_contests: Épreuves de cet événement
modify: Paramètres modify: Paramètres
new_contest: Ajouter une épreuve à cet événement new_contest: Ajouter une épreuve à cet événement
shared_scoreboard_disabled: Le classement partagé n'est pas activé
open_shared_scoreboard: Ouvrir le classement partagé
helpers: helpers:
badges: badges:
registration: "auto-inscription" registration: "auto-inscription"

View File

@@ -55,6 +55,7 @@ Rails.application.routes.draw do
post "update_contestants", to: "users#update_contestants" post "update_contestants", to: "users#update_contestants"
get "public/:id", to: "contests#scoreboard" get "public/:id", to: "contests#scoreboard"
get "public/event/:id", to: "contests#scoreboard"
get "public/:id/offline", to: "contests#offline_new" get "public/:id/offline", to: "contests#offline_new"
post "public/:id/offline", to: "contests#offline_create" post "public/:id/offline", to: "contests#offline_create"
get "public/:id/offline/:token", to: "contests#offline_edit" get "public/:id/offline/:token", to: "contests#offline_edit"

View File

@@ -0,0 +1,5 @@
class AddSlugToEvent < ActiveRecord::Migration[8.0]
def change
add_column :events, :slug, :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: 2026_07_26_114604) do ActiveRecord::Schema[8.0].define(version: 2026_07_30_055752) 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
@@ -126,6 +126,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_07_26_114604) do
t.integer "user_id", null: false t.integer "user_id", null: false
t.boolean "shared_scoreboard" t.boolean "shared_scoreboard"
t.string "lang", default: "en" t.string "lang", default: "en"
t.string "slug"
t.index ["user_id"], name: "index_events_on_user_id" t.index ["user_id"], name: "index_events_on_user_id"
end end

View File

@@ -6,6 +6,7 @@
# lang :string default("en") # lang :string default("en")
# name :string # name :string
# shared_scoreboard :boolean # shared_scoreboard :boolean
# slug :string
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# user_id :integer not null # user_id :integer not null

View File

@@ -6,6 +6,7 @@
# lang :string default("en") # lang :string default("en")
# name :string # name :string
# shared_scoreboard :boolean # shared_scoreboard :boolean
# slug :string
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# user_id :integer not null # user_id :integer not null