From 35ad7da355eb93defc21c1cf9403f4da5ba6c029 Mon Sep 17 00:00:00 2001 From: sto Date: Wed, 29 Oct 2025 17:20:38 +0100 Subject: [PATCH] Add offline model and "new" form/controller --- app/controllers/contests_controller.rb | 17 +++++++++++- app/models/contest.rb | 1 + app/models/offline.rb | 27 ++++++++++++++++++++ app/policies/contest_policy.rb | 16 ++++++++++++ app/views/contests/offline_new.html.slim | 25 ++++++++++++++++++ config/routes.rb | 4 +++ db/migrate/20251029155116_create_offlines.rb | 10 ++++++++ db/schema.rb | 12 ++++++++- spec/factories/offlines.rb | 24 +++++++++++++++++ spec/models/offline_spec.rb | 24 +++++++++++++++++ 10 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 app/models/offline.rb create mode 100644 app/views/contests/offline_new.html.slim create mode 100644 db/migrate/20251029155116_create_offlines.rb create mode 100644 spec/factories/offlines.rb create mode 100644 spec/models/offline_spec.rb diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 0212027..9d49c32 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -1,6 +1,6 @@ class ContestsController < ApplicationController before_action :set_contest, only: %i[ destroy edit show update ] - skip_before_action :require_authentication, only: %i[ scoreboard ] + skip_before_action :require_authentication, only: %i[ scoreboard offline_new ] def index authorize :contest @@ -99,6 +99,21 @@ class ContestsController < ApplicationController render :scoreboard end + def offline_new + @contest = Contest.find_by(slug: params[:id]) + unless @contest && @contest.public + 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 + end + private def set_badges diff --git a/app/models/contest.rb b/app/models/contest.rb index b5ef310..45cb301 100644 --- a/app/models/contest.rb +++ b/app/models/contest.rb @@ -31,6 +31,7 @@ class Contest < ApplicationRecord has_many :contestants, dependent: :destroy has_many :puzzles, dependent: :destroy has_many :messages, dependent: :destroy + has_many :offlines, dependent: :destroy friendly_id :name, use: :slugged diff --git a/app/models/offline.rb b/app/models/offline.rb new file mode 100644 index 0000000..f0ad1ad --- /dev/null +++ b/app/models/offline.rb @@ -0,0 +1,27 @@ +# == Schema Information +# +# Table name: offlines +# +# id :integer not null, primary key +# end_time :datetime +# start_time :datetime not null +# created_at :datetime not null +# updated_at :datetime not null +# contest_id :integer not null +# +# Indexes +# +# index_offlines_on_contest_id (contest_id) +# +# Foreign Keys +# +# contest_id (contest_id => contests.id) +# +class Offline < ApplicationRecord + belongs_to :contest + + has_one_attached :start_image + has_one_attached :end_image + + validates :start_time, presence: true +end diff --git a/app/policies/contest_policy.rb b/app/policies/contest_policy.rb index 0b96f35..5e6a300 100644 --- a/app/policies/contest_policy.rb +++ b/app/policies/contest_policy.rb @@ -47,6 +47,22 @@ class ContestPolicy < ApplicationPolicy record.user.id == user.id || user.admin? end + def offline_new? + true + end + + def offline_create? + true + end + + def offline_edit? + true + end + + def offline_update? + true + end + def scoreboard? true end diff --git a/app/views/contests/offline_new.html.slim b/app/views/contests/offline_new.html.slim new file mode 100644 index 0000000..5d85d2d --- /dev/null +++ b/app/views/contests/offline_new.html.slim @@ -0,0 +1,25 @@ += form_with model: @offline, url: "/public/#{@contest.id}/offline" do |form| + .row.mb-3 + .col + .form-text.mb-1 + = t("puzzles.image_select") + = form.file_field :start_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-3 + .col + = form.submit t("helpers.buttons.add"), class: "btn btn-primary" \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index b5c9edd..54b74f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,4 +32,8 @@ Rails.application.routes.draw do post "message", to: "messages#create" get "public/:id", to: "contests#scoreboard" + get "public/:id/offline", to: "contests#offline_new" + post "public/:id/offline", to: "contests#offline_create" + get "public/:id/offline/:offline_id", to: "contests#offline_edit" + post "public/:id/offline/:offline_id", to: "contests#offline_update" end diff --git a/db/migrate/20251029155116_create_offlines.rb b/db/migrate/20251029155116_create_offlines.rb new file mode 100644 index 0000000..60b63b7 --- /dev/null +++ b/db/migrate/20251029155116_create_offlines.rb @@ -0,0 +1,10 @@ +class CreateOfflines < ActiveRecord::Migration[8.0] + def change + create_table :offlines do |t| + t.timestamps + t.belongs_to :contest, null: false, foreign_key: true + t.datetime :start_time, null: false + t.datetime :end_time + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e5e6b66..4218f98 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_10_28_131431) do +ActiveRecord::Schema[8.0].define(version: 2025_10_29_155116) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -125,6 +125,15 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_28_131431) do t.index ["contest_id"], name: "index_messages_on_contest_id" end + create_table "offlines", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "contest_id", null: false + t.datetime "start_time", null: false + t.datetime "end_time" + t.index ["contest_id"], name: "index_offlines_on_contest_id" + end + create_table "puzzles", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false @@ -165,6 +174,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_28_131431) do add_foreign_key "contestants", "contests" add_foreign_key "contests", "users" add_foreign_key "messages", "contests" + add_foreign_key "offlines", "contests" add_foreign_key "puzzles", "contests" add_foreign_key "sessions", "users" end diff --git a/spec/factories/offlines.rb b/spec/factories/offlines.rb new file mode 100644 index 0000000..dc0c4b7 --- /dev/null +++ b/spec/factories/offlines.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: offlines +# +# id :integer not null, primary key +# end_time :datetime +# start_time :datetime not null +# created_at :datetime not null +# updated_at :datetime not null +# contest_id :integer not null +# +# Indexes +# +# index_offlines_on_contest_id (contest_id) +# +# Foreign Keys +# +# contest_id (contest_id => contests.id) +# +FactoryBot.define do + factory :offline do + + end +end diff --git a/spec/models/offline_spec.rb b/spec/models/offline_spec.rb new file mode 100644 index 0000000..913ae79 --- /dev/null +++ b/spec/models/offline_spec.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: offlines +# +# id :integer not null, primary key +# end_time :datetime +# start_time :datetime not null +# created_at :datetime not null +# updated_at :datetime not null +# contest_id :integer not null +# +# Indexes +# +# index_offlines_on_contest_id (contest_id) +# +# Foreign Keys +# +# contest_id (contest_id => contests.id) +# +require 'rails_helper' + +RSpec.describe Offline, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end