Offline participation: implement completed puzzle methods
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
1
app/views/contests/offline_completed.html.slim
Normal file
1
app/views/contests/offline_completed.html.slim
Normal file
@@ -0,0 +1 @@
|
|||||||
|
| TODO
|
||||||
26
app/views/contests/offline_edit.html.slim
Normal file
26
app/views/contests/offline_edit.html.slim
Normal 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"
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
5
db/migrate/20251031095604_add_completed_to_offline.rb
Normal file
5
db/migrate/20251031095604_add_completed_to_offline.rb
Normal 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
3
db/schema.rb
generated
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user