Implement offline POST method for starting an offline participation
This commit is contained in:
@@ -30,8 +30,12 @@ class ApplicationController < ActionController::Base
|
|||||||
def user_not_authorized(exception)
|
def user_not_authorized(exception)
|
||||||
policy_name = exception.policy.class.to_s.underscore
|
policy_name = exception.policy.class.to_s.underscore
|
||||||
|
|
||||||
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
if current_user
|
||||||
redirect_back_or_to(root_path)
|
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
||||||
|
redirect_back_or_to(root_path)
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_found
|
def not_found
|
||||||
|
|||||||
@@ -1,6 +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 ]
|
||||||
skip_before_action :require_authentication, only: %i[ scoreboard offline_new ]
|
before_action :offline_setup, only: %i[ offline_new offline_create ]
|
||||||
|
skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create ]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :contest
|
authorize :contest
|
||||||
@@ -100,22 +101,30 @@ class ContestsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def offline_new
|
def offline_new
|
||||||
@contest = Contest.find_by(slug: params[:id])
|
authorize @contest
|
||||||
unless @contest && @contest.offline_form
|
|
||||||
skip_authorization
|
|
||||||
not_found and return
|
|
||||||
end
|
|
||||||
authorize :contest
|
|
||||||
|
|
||||||
I18n.locale = @contest.lang
|
|
||||||
|
|
||||||
@title = I18n.t("contests.scoreboard.title", name: @contest.name)
|
|
||||||
|
|
||||||
@offline = Offline.new
|
@offline = Offline.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def offline_create
|
||||||
|
authorize @contest
|
||||||
|
@offline = Offline.new(offline_start_params)
|
||||||
|
@offline.contest = @contest
|
||||||
|
@offline.start_time = Time.now()
|
||||||
|
if @offline.save
|
||||||
|
redirect_to "/public/#{@contest.friendly_id}/offline/test"
|
||||||
|
else
|
||||||
|
render :offline_new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def offline_setup
|
||||||
|
@contest = Contest.find_by(slug: params[:id])
|
||||||
|
I18n.locale = @contest.lang
|
||||||
|
@title = I18n.t("contests.scoreboard.title", name: @contest.name)
|
||||||
|
end
|
||||||
|
|
||||||
def set_badges
|
def set_badges
|
||||||
@badges = []
|
@badges = []
|
||||||
@badges.push(t("helpers.badges.team")) if @contest.team
|
@badges.push(t("helpers.badges.team")) if @contest.team
|
||||||
@@ -139,4 +148,8 @@ class ContestsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def offline_start_params
|
||||||
|
params.expect(offline: [ :name, :start_image ])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# end_time :datetime
|
# end_time :datetime
|
||||||
|
# name :string not null
|
||||||
# start_time :datetime not null
|
# start_time :datetime not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
@@ -23,5 +24,17 @@ class Offline < ApplicationRecord
|
|||||||
has_one_attached :start_image
|
has_one_attached :start_image
|
||||||
has_one_attached :end_image
|
has_one_attached :end_image
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
validates :start_time, presence: true
|
validates :start_time, presence: true
|
||||||
|
|
||||||
|
validate :start_image_is_present
|
||||||
|
|
||||||
|
def start_image_is_present
|
||||||
|
logger = Logger.new(STDOUT)
|
||||||
|
logger.info "TESTddfzefzef"
|
||||||
|
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
|
||||||
|
|||||||
@@ -47,24 +47,28 @@ class ContestPolicy < ApplicationPolicy
|
|||||||
record.user.id == user.id || user.admin?
|
record.user.id == user.id || user.admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def offline?
|
||||||
|
record.offline_form
|
||||||
|
end
|
||||||
|
|
||||||
def offline_new?
|
def offline_new?
|
||||||
true
|
offline?
|
||||||
end
|
end
|
||||||
|
|
||||||
def offline_create?
|
def offline_create?
|
||||||
true
|
offline?
|
||||||
end
|
end
|
||||||
|
|
||||||
def offline_edit?
|
def offline_edit?
|
||||||
true
|
offline?
|
||||||
end
|
end
|
||||||
|
|
||||||
def offline_update?
|
def offline_update?
|
||||||
true
|
offline?
|
||||||
end
|
end
|
||||||
|
|
||||||
def scoreboard?
|
def scoreboard?
|
||||||
true
|
record.public
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_csv?
|
def upload_csv?
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
= form_with model: @offline, url: "/public/#{@contest.id}/offline" do |form|
|
= form_with model: @offline, url: "/public/#{@contest.friendly_id}/offline" do |form|
|
||||||
|
.row.mb-3
|
||||||
|
.col
|
||||||
|
.form-floating
|
||||||
|
= form.text_field :name, autocomplete: "off", class: "form-control"
|
||||||
|
= form.label :name, class: "required"
|
||||||
.row.mb-3
|
.row.mb-3
|
||||||
.col
|
.col
|
||||||
.form-text.mb-1
|
.form-text.mb-1
|
||||||
= t("puzzles.image_select")
|
= t("offlines.start_image_select")
|
||||||
= form.file_field :start_image, accept: "image/*", class: "form-control"
|
= form.file_field :start_image, 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")
|
||||||
@@ -20,6 +25,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setMaxUploadSize();
|
setMaxUploadSize();
|
||||||
.row.mt-3
|
.row.mt-4
|
||||||
.col
|
.col
|
||||||
= form.submit t("helpers.buttons.add"), class: "btn btn-primary"
|
= form.submit t("helpers.buttons.start"), class: "btn btn-primary"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
ActionView::Base.field_error_proc = proc do |html_tag, instance|
|
ActionView::Base.field_error_proc = proc do |html_tag, instance|
|
||||||
if html_tag.include? "<label"
|
if (html_tag.include? "<label") || (html_tag.include? "<input accept=\"image")
|
||||||
appended_html = ""
|
appended_html = ""
|
||||||
if instance.error_message.is_a?(Array)
|
if instance.error_message.is_a?(Array)
|
||||||
appended_html = "<div class='error-message form-text'>#{instance.error_message.map(&:capitalize).uniq.join(", ")}</div>"
|
appended_html = "<div class='error-message form-text'>#{instance.error_message.map(&:capitalize).uniq.join(", ")}</div>"
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ en:
|
|||||||
processed: Processed?
|
processed: Processed?
|
||||||
text: Content
|
text: Content
|
||||||
time: Time
|
time: Time
|
||||||
|
offline:
|
||||||
|
name: Your name
|
||||||
puzzle:
|
puzzle:
|
||||||
brand: Brand
|
brand: Brand
|
||||||
image: Image
|
image: Image
|
||||||
@@ -112,6 +114,12 @@ en:
|
|||||||
blank: "No file selected"
|
blank: "No file selected"
|
||||||
empty: "This file is empty"
|
empty: "This file is empty"
|
||||||
not_a_csv_file: "it must be a CSV file"
|
not_a_csv_file: "it must be a CSV file"
|
||||||
|
offline:
|
||||||
|
attributes:
|
||||||
|
name:
|
||||||
|
blank: Please enter a name for your participation
|
||||||
|
start_image:
|
||||||
|
blank: Please upload an image
|
||||||
puzzle:
|
puzzle:
|
||||||
attributes:
|
attributes:
|
||||||
name:
|
name:
|
||||||
@@ -216,6 +224,7 @@ en:
|
|||||||
refresh: Refresh
|
refresh: Refresh
|
||||||
sign_in: Sign in
|
sign_in: Sign in
|
||||||
save: Save
|
save: Save
|
||||||
|
start: Click here to start your participation
|
||||||
field: Field
|
field: Field
|
||||||
none: No field selected
|
none: No field selected
|
||||||
rank: Rank
|
rank: Rank
|
||||||
@@ -232,6 +241,8 @@ en:
|
|||||||
home: My contests
|
home: My contests
|
||||||
settings: Settings
|
settings: Settings
|
||||||
log_out: Log out
|
log_out: Log out
|
||||||
|
offlines:
|
||||||
|
start_image_select: Enter your 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
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ fr:
|
|||||||
processed: Traité ?
|
processed: Traité ?
|
||||||
text: Contenu
|
text: Contenu
|
||||||
time: Temps
|
time: Temps
|
||||||
|
offline:
|
||||||
|
name: Ton nom ou pseudo
|
||||||
puzzle:
|
puzzle:
|
||||||
brand: Marque
|
brand: Marque
|
||||||
image: Image
|
image: Image
|
||||||
@@ -83,6 +85,12 @@ fr:
|
|||||||
blank: "Aucun fichier sélectionné"
|
blank: "Aucun fichier sélectionné"
|
||||||
empty: "Ce fichier est vide"
|
empty: "Ce fichier est vide"
|
||||||
not_a_csv_file: "Le fichier doit être au format CSV"
|
not_a_csv_file: "Le fichier doit être au format CSV"
|
||||||
|
offline:
|
||||||
|
attributes:
|
||||||
|
name:
|
||||||
|
blank: Tu dois entrer un nom pour pouvoir participer
|
||||||
|
start_image:
|
||||||
|
blank: Tu dois inclure cette image pour pouvoir participer
|
||||||
puzzle:
|
puzzle:
|
||||||
attributes:
|
attributes:
|
||||||
name:
|
name:
|
||||||
@@ -187,6 +195,7 @@ fr:
|
|||||||
refresh: Rafraîchir
|
refresh: Rafraîchir
|
||||||
sign_in: Se connecter
|
sign_in: Se connecter
|
||||||
save: Modifier
|
save: Modifier
|
||||||
|
start: Clique ici pour démarrer ta participation
|
||||||
field: Champ
|
field: Champ
|
||||||
none: Aucun champ sélectionné
|
none: Aucun champ sélectionné
|
||||||
rank: Rang
|
rank: Rang
|
||||||
@@ -203,6 +212,8 @@ fr:
|
|||||||
home: Mes concours
|
home: Mes concours
|
||||||
settings: Paramètres
|
settings: Paramètres
|
||||||
log_out: Déconnexion
|
log_out: Déconnexion
|
||||||
|
offlines:
|
||||||
|
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:
|
||||||
notice: Puzzle supprimé
|
notice: Puzzle supprimé
|
||||||
|
|||||||
5
db/migrate/20251030094151_add_name_to_offline.rb
Normal file
5
db/migrate/20251030094151_add_name_to_offline.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class AddNameToOffline < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
add_column :offlines, :name, :string, null: false
|
||||||
|
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_092221) do
|
ActiveRecord::Schema[8.0].define(version: 2025_10_30_094151) 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
|
||||||
@@ -132,6 +132,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_30_092221) do
|
|||||||
t.integer "contest_id", null: false
|
t.integer "contest_id", null: false
|
||||||
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.index ["contest_id"], name: "index_offlines_on_contest_id"
|
t.index ["contest_id"], name: "index_offlines_on_contest_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# end_time :datetime
|
# end_time :datetime
|
||||||
|
# name :string not null
|
||||||
# start_time :datetime not null
|
# start_time :datetime not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# end_time :datetime
|
# end_time :datetime
|
||||||
|
# name :string not null
|
||||||
# start_time :datetime not null
|
# start_time :datetime not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
|||||||
Reference in New Issue
Block a user