Offline participation: implement completed puzzle methods
Some checks failed
CI / scan_ruby (push) Successful in 20s
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
2025-10-31 11:55:28 +01:00
parent 37a65526e4
commit ff5f387a87
13 changed files with 110 additions and 17 deletions

View File

@@ -1,7 +1,7 @@
class ContestsController < ApplicationController class ContestsController < ApplicationController
before_action :set_contest, only: %i[ destroy edit show update ] before_action :set_contest, only: %i[ destroy edit show update ]
before_action :offline_setup, only: %i[ offline_new offline_create ] before_action :offline_setup, only: %i[ offline_new offline_create offline_edit offline_update offline_completed ]
skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create ] skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create offline_edit offline_update offline_completed ]
def index def index
authorize :contest authorize :contest
@@ -111,12 +111,47 @@ class ContestsController < ApplicationController
@offline.contest = @contest @offline.contest = @contest
@offline.start_time = Time.now() @offline.start_time = Time.now()
if @offline.save if @offline.save
redirect_to "/public/#{@contest.friendly_id}/offline/test" redirect_to "/public/#{@contest.friendly_id}/offline/#{@offline.generate_token_for(:token)}"
else else
render :offline_new, status: :unprocessable_entity render :offline_new, status: :unprocessable_entity
end end
end end
def offline_edit
authorize @contest
@offline = Offline.find_by_token_for(:token, params[:token])
if !@offline
not_found and return
end
end
def offline_update
authorize @contest
@offline = Offline.find_by_token_for(:token, params[:token])
if !@offline
not_found and return
end
@offline.completed = true
@offline.images.attach(params[:offline][:end_image])
if @offline.save
redirect_to "/public/#{@contest.friendly_id}/offline/#{@offline.generate_token_for(:token)}/completed"
else
render :offline_edit, status: :unprocessable_entity
end
end
def offline_completed
authorize @contest
@offline = Offline.find_by_token_for(:token, params[:token])
if !@offline
not_found and return
end
end
private private
def offline_setup def offline_setup
@@ -150,6 +185,10 @@ class ContestsController < ApplicationController
end end
def offline_start_params def offline_start_params
params.expect(offline: [ :name, :start_image ]) params.expect(offline: [ :name, :images ])
end
def offline_end_params
params.expect(offline: [ :completed, :end_image ])
end end
end end

View File

@@ -3,6 +3,7 @@
# Table name: offlines # Table name: offlines
# #
# id :integer not null, primary key # id :integer not null, primary key
# completed :boolean
# end_time :datetime # end_time :datetime
# name :string not null # name :string not null
# start_time :datetime not null # start_time :datetime not null
@@ -21,20 +22,25 @@
class Offline < ApplicationRecord class Offline < ApplicationRecord
belongs_to :contest belongs_to :contest
has_one_attached :start_image has_many_attached :images
has_one_attached :end_image
generates_token_for :token
validates :name, presence: true validates :name, presence: true
validates :start_time, presence: true validates :start_time, presence: true
validate :end_image_is_present
validate :start_image_is_present validate :start_image_is_present
def end_image_is_present
if self.completed && self.images.length < 2
errors.add(:end_image, I18n.t("activerecord.errors.models.offline.attributes.end_image.blank"))
end
end
def start_image_is_present def start_image_is_present
logger = Logger.new(STDOUT) if !self.images.attached?
logger.info "TESTddfzefzef" errors.add(:images, I18n.t("activerecord.errors.models.offline.attributes.start_image.blank"))
logger.info self.start_image.attached?
if !self.start_image.attached?
errors.add(:start_image, I18n.t("activerecord.errors.models.offline.attributes.start_image.blank"))
end end
end end
end end

View File

@@ -67,6 +67,10 @@ class ContestPolicy < ApplicationPolicy
offline? offline?
end end
def offline_completed?
offline?
end
def scoreboard? def scoreboard?
record.public record.public
end end

View File

@@ -0,0 +1 @@
| TODO

View File

@@ -0,0 +1,26 @@
= form_with model: @offline, url: "/public/#{@contest.friendly_id}/offline/#{@offline.generate_token_for(:token)}" do |form|
= form.hidden_field :completed
.row.mb-3
.col
.form-text.mb-1
= t("offlines.end_image_select")
= form.file_field :end_image, accept: "image/*", class: "form-control"
.form-text.error-message style="display: none;" id="image-error-message"
= t("puzzles.form.file_too_big")
javascript:
function setMaxUploadSize() {
const el = document.querySelector('input[type="file"]');
el.onchange = function() {
if(this.files[0].size > 2 * 1024 * 1024) {
document.getElementById('image-error-message').style.display = 'block';
this.value = "";
} else {
document.getElementById('image-error-message').style.display = 'none';
}
};
}
setMaxUploadSize();
.row.mt-4
.col
= form.submit t("helpers.buttons.end"), class: "btn btn-primary"

View File

@@ -8,7 +8,7 @@
.col .col
.form-text.mb-1 .form-text.mb-1
= t("offlines.start_image_select") = t("offlines.start_image_select")
= form.file_field :start_image, accept: "image/*", class: "form-control" = form.file_field :images, accept: "image/*", class: "form-control"
.form-text.error-message style="display: none;" id="image-error-message" .form-text.error-message style="display: none;" id="image-error-message"
= t("puzzles.form.file_too_big") = t("puzzles.form.file_too_big")
javascript: javascript:

View File

@@ -116,6 +116,8 @@ en:
not_a_csv_file: "it must be a CSV file" not_a_csv_file: "it must be a CSV file"
offline: offline:
attributes: attributes:
end_image:
blank: Please upload an image
name: name:
blank: Please enter a name for your participation blank: Please enter a name for your participation
start_image: start_image:
@@ -218,6 +220,7 @@ en:
create: "Create" create: "Create"
delete: "Delete" delete: "Delete"
edit: "Edit" edit: "Edit"
end: Click here to submit your completion
export: Export export: Export
import: CSV Import import: CSV Import
open: Open open: Open
@@ -242,7 +245,8 @@ en:
settings: Settings settings: Settings
log_out: Log out log_out: Log out
offlines: offlines:
start_image_select: Enter your photo of the puzzle with the provided code written on a paper before starting it end_image_select: Take a photo of your completed puzzle
start_image_select: Take a photo of the puzzle with the provided code written on a paper before starting it
puzzles: puzzles:
destroy: destroy:
notice: Puzzle deleted notice: Puzzle deleted

View File

@@ -87,6 +87,8 @@ fr:
not_a_csv_file: "Le fichier doit être au format CSV" not_a_csv_file: "Le fichier doit être au format CSV"
offline: offline:
attributes: attributes:
end_image:
blank: Tu dois inclure cette image pour pouvoir valider ton puzzle complété
name: name:
blank: Tu dois entrer un nom pour pouvoir participer blank: Tu dois entrer un nom pour pouvoir participer
start_image: start_image:
@@ -189,6 +191,7 @@ fr:
create: "Créer" create: "Créer"
delete: "Supprimer" delete: "Supprimer"
edit: "Modifier" edit: "Modifier"
end: Clique ici pour valider ta complétion du puzzle
export: Exporter export: Exporter
import: Importer un CSV import: Importer un CSV
open: Détails open: Détails
@@ -213,6 +216,7 @@ fr:
settings: Paramètres settings: Paramètres
log_out: Déconnexion log_out: Déconnexion
offlines: offlines:
end_image_select: Prends une photo du puzzle une fois complété
start_image_select: Prends une photo du puzzle avant de le commencer, avec le code donné par l'organisateur.ice écrit sur du papier start_image_select: Prends une photo du puzzle avant de le commencer, avec le code donné par l'organisateur.ice écrit sur du papier
puzzles: puzzles:
destroy: destroy:

View File

@@ -34,6 +34,7 @@ Rails.application.routes.draw do
get "public/:id", to: "contests#scoreboard" get "public/: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/:offline_id", to: "contests#offline_edit" get "public/:id/offline/:token", to: "contests#offline_edit"
post "public/:id/offline/:offline_id", to: "contests#offline_update" patch "public/:id/offline/:token", to: "contests#offline_update"
get "public/:id/offline/:token/completed", to: "contests#offline_completed"
end end

View File

@@ -0,0 +1,5 @@
class AddCompletedToOffline < ActiveRecord::Migration[8.0]
def change
add_column :offlines, :completed, :boolean
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_10_30_094151) do ActiveRecord::Schema[8.0].define(version: 2025_10_31_095604) 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
@@ -133,6 +133,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_30_094151) do
t.datetime "start_time", null: false t.datetime "start_time", null: false
t.datetime "end_time" t.datetime "end_time"
t.string "name", null: false t.string "name", null: false
t.boolean "completed"
t.index ["contest_id"], name: "index_offlines_on_contest_id" t.index ["contest_id"], name: "index_offlines_on_contest_id"
end end

View File

@@ -3,6 +3,7 @@
# Table name: offlines # Table name: offlines
# #
# id :integer not null, primary key # id :integer not null, primary key
# completed :boolean
# end_time :datetime # end_time :datetime
# name :string not null # name :string not null
# start_time :datetime not null # start_time :datetime not null
@@ -20,6 +21,6 @@
# #
FactoryBot.define do FactoryBot.define do
factory :offline do factory :offline do
name { "test" }
end end
end end

View File

@@ -3,6 +3,7 @@
# Table name: offlines # Table name: offlines
# #
# id :integer not null, primary key # id :integer not null, primary key
# completed :boolean
# end_time :datetime # end_time :datetime
# name :string not null # name :string not null
# start_time :datetime not null # start_time :datetime not null