Events: initial implementation
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-26 14:01:29 +02:00
parent fa8880abfc
commit 35b304d913
26 changed files with 389 additions and 24 deletions

View File

@@ -0,0 +1,56 @@
class EventsController < ApplicationController
before_action :set_event, only: %i[ destroy show ]
def index
authorize :event
@events = current_user.events.order(created_at: :desc)
@contests_without_event = current_user.contests.where(event: nil).size
@title = I18n.t("events.index.title", username: current_user.username)
end
def new
authorize :event
@event = Event.new
@title = I18n.t("events.new.title")
@nonav = true
end
def create
authorize :event
@event = Event.new(new_event_params)
@event.lang = @current_user.lang
@event.user_id = current_user.id
if @event.save
redirect_to "/events/#{@event.id}", notice: t("events.new.notice")
else
@title = I18n.t("events.new.title")
@nonav = true
render :new, status: :unprocessable_entity
end
end
def show
authorize @event
@contests = @event.contests
@title = @event.name
end
def destroy
authorize @event
@event.destroy
redirect_to events_path, notice: t("events.destroy.notice")
end
def set_event
@event = Event.find(params[:id])
end
def new_event_params
params.expect(event: [ :name, :shared_scoreboard ])
end
end

View File

@@ -0,0 +1,2 @@
module EventsHelper
end

View File

@@ -20,21 +20,25 @@
# team :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# event_id :integer
# user_id :integer not null
#
# Indexes
#
# index_contests_on_slug (slug) UNIQUE
# index_contests_on_user_id (user_id)
# index_contests_on_event_id (event_id)
# index_contests_on_slug (slug) UNIQUE
# index_contests_on_user_id (user_id)
#
# Foreign Keys
#
# user_id (user_id => users.id)
# event_id (event_id => events.id)
# user_id (user_id => users.id)
#
class Contest < ApplicationRecord
extend FriendlyId
belongs_to :user
belongs_to :event, optional: true
has_many :categories
has_many :completions, dependent: :destroy
has_many :contestants, dependent: :destroy

View File

@@ -2,16 +2,15 @@
#
# Table name: contestants
#
# id :integer not null, primary key
# display_time :string
# email :string
# name :string
# projected_time :string
# qrcode :string
# time_seconds :integer
# created_at :datetime not null
# updated_at :datetime not null
# contest_id :integer not null
# id :integer not null, primary key
# display_time :string
# email :string
# name :string
# qrcode :string
# time_seconds :integer
# created_at :datetime not null
# updated_at :datetime not null
# contest_id :integer not null
#
# Indexes
#

26
app/models/event.rb Normal file
View File

@@ -0,0 +1,26 @@
# == Schema Information
#
# Table name: events
#
# id :integer not null, primary key
# lang :string default("en")
# name :string
# shared_scoreboard :boolean
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer not null
#
# Indexes
#
# index_events_on_user_id (user_id)
#
# Foreign Keys
#
# user_id (user_id => users.id)
#
class Event < ApplicationRecord
belongs_to :user
has_many :contests, dependent: :destroy
validates :name, presence: true
end

View File

@@ -17,6 +17,7 @@
# index_users_on_email_address (email_address) UNIQUE
#
class User < ApplicationRecord
has_many :events, dependent: :destroy
has_many :contests, dependent: :destroy
has_many :sessions, dependent: :destroy
has_secure_password

View File

@@ -0,0 +1,25 @@
class EventPolicy < ApplicationPolicy
def owner_or_admin
if record == :event
true
else
record.user.id == user.id || user.admin?
end
end
def index?
owner_or_admin
end
def new?
owner_or_admin
end
def create?
owner_or_admin
end
def show?
owner_or_admin
end
end

View File

@@ -0,0 +1,22 @@
= form_with model: event, url: url, method: method 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
.col
.form-check.form-switch
= form.check_box :shared_scoreboard, class: "form-check-input"
= form.label :shared_scoreboard
.form-text = t("activerecord.attributes.event.shared_scoreboard_description")
.alert.alert-primary role="alert"
= t("events.form.shared_scoreboard_message")
.row.mt-4
.col
- if method == :patch
= link_to t("helpers.buttons.delete"), event_path(event), data: { turbo_method: :delete }, class: "btn btn-danger me-2"
= form.submit submit_text, class: "btn btn-primary"

View File

@@ -0,0 +1,36 @@
.row
.col
h4.mb-3
= t("events.index.subtitle")
.float-end
a.btn.btn-primary.mb-4 href=new_event_path
= t("events.index.new")
.alert.alert-primary role="alert"
= t("events.index.message")
- @events.each do |event|
.row.mt-3
.col
css:
.card:hover { background-color: lightblue; }
.card.h-100
.card-header
= event.name
.card-body
.card-text.mb-2
= "#{event.contests.size} " + t("events.index.contests") if event.contests.size > 1
= "#{event.contests.size} " + t("events.index.contest") if event.contests.size <= 1
a.stretched-link href=event_path(event)
.row.mt-3
.col
css:
.card:hover { background-color: lightblue; }
.card.h-100
.card-header
= t("events.index.my_contests_outside_events")
.card-body
.card-text.mb-2
= "#{@contests_without_event} " + t("events.index.contests") if @contests_without_event > 1
= "#{@contests_without_event} " + t("events.index.contest") if @contests_without_event <= 1
a.stretched-link href=contests_path

View File

@@ -0,0 +1 @@
= render "form", event: @event, submit_text: t("helpers.buttons.add"), method: :post, url: "/events"

View File

@@ -0,0 +1,28 @@
.row
.col
h4.mb-3
= t("contests.index.manage_contests")
.float-end
a.btn.btn-primary.mb-4 href=new_contest_path
= t("contests.index.new_contest")
.row.row-cols-1.row-cols-md-3.g-4
- @contests.each do |contest|
.col
css:
.card:hover { background-color: lightblue; }
.card.h-100
.card-header
= contest.name
.card-body
.card-text.mb-2
= "#{contest.puzzles.length} #{t('puzzles.singular')}" if contest.puzzles.length <= 1
= "#{contest.puzzles.length} #{t('puzzles.plural')}" if contest.puzzles.length > 1
= " - #{contest.contestants.length} #{t('contestants.singular')}" if contest.contestants.length <= 1
= " - #{contest.contestants.length} #{t('contestants.plural')}" if contest.contestants.length > 1
.row
.col
- contest.puzzles.each do |puzzle|
- if puzzle.image.attached?
= image_tag puzzle.image, style: "max-height: 50px;", class: "mb-2 me-2"
a.stretched-link href=contest_path(contest)