diff --git a/app/contracts/cups/pair_contract.rb b/app/contracts/cups/pair_contract.rb
new file mode 100644
index 00000000..d2de1ee2
--- /dev/null
+++ b/app/contracts/cups/pair_contract.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Cups
+ class PairContract < ApplicationContract
+ config.messages.namespace = :cups_pair
+
+ params do
+ optional(:home_name).filled(:string)
+ optional(:visitor_name).filled(:string)
+ optional(:start_at)
+ optional(:points)
+ end
+ end
+end
diff --git a/app/controllers/admin/cups/pairs_controller.rb b/app/controllers/admin/cups/pairs_controller.rb
new file mode 100644
index 00000000..f1204f88
--- /dev/null
+++ b/app/controllers/admin/cups/pairs_controller.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Admin
+ module Cups
+ class PairsController < Admin::BaseController
+ include Deps[
+ create_form: 'forms.cups.pairs.create',
+ update_form: 'forms.cups.pairs.update'
+ ]
+
+ before_action :find_cups_pairs, only: %i[index]
+ before_action :find_cups_round, only: %i[new edit create update]
+ before_action :find_cups_pair, only: %i[edit update]
+
+ def index; end
+
+ def new
+ @cups_pair = ::Cups::Pair.new
+ end
+
+ def edit; end
+
+ def create
+ case create_form.call(cups_round: @cups_round, params: cups_pair_params)
+ in { errors: errors }
+ redirect_to new_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id]), alert: errors
+ else
+ redirect_to(
+ admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]),
+ notice: t('controllers.admin.cups.pairs.create.success')
+ )
+ end
+ end
+
+ def update
+ case update_form.call(cups_pair: @cups_pair, params: cups_pair_params)
+ in { errors: errors }
+ redirect_to(
+ edit_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: @cups_pair.id),
+ alert: errors
+ )
+ else
+ redirect_to(
+ admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]),
+ notice: t('controllers.admin.cups.pairs.update.success')
+ )
+ end
+ end
+
+ private
+
+ def find_cups_pairs
+ @cups_pairs =
+ ::Cups::Pair
+ .where(cups_round_id: params[:cups_round_id])
+ .order(start_at: :asc)
+ .hashable_pluck(:id, :home_name, :visitor_name, :start_at, :points)
+ end
+
+ def find_cups_round
+ @cups_round = ::Cups::Round.find(params[:cups_round_id])
+ end
+
+ def find_cups_pair
+ @cups_pair = @cups_round.cups_pairs.find(params[:id])
+ end
+
+ # rubocop: disable Metrics/AbcSize
+ def cups_pair_params
+ params
+ .require(:cups_pair)
+ .permit(:home_name, :visitor_name)
+ .to_h
+ .merge(
+ start_at: params[:cups_pair][:start_at].present? ? DateTime.parse(params[:cups_pair][:start_at]) : nil,
+ points: params[:cups_pair][:points].present? ? params[:cups_pair][:points].split('-') : []
+ )
+ end
+ # rubocop: enable Metrics/AbcSize
+ end
+ end
+end
diff --git a/app/controllers/admin/cups/rounds_controller.rb b/app/controllers/admin/cups/rounds_controller.rb
index 23202dd6..48a85642 100644
--- a/app/controllers/admin/cups/rounds_controller.rb
+++ b/app/controllers/admin/cups/rounds_controller.rb
@@ -27,7 +27,11 @@ def create
private
def find_cups_rounds
- @cups_rounds = ::Cups::Round.where(cup_id: params[:cup_id]).order(position: :asc)
+ @cups_rounds =
+ ::Cups::Round
+ .where(cup_id: params[:cup_id])
+ .order(position: :asc)
+ .hashable_pluck(:id, :name, :position)
end
def find_cup
diff --git a/app/forms/cups/pairs/create_form.rb b/app/forms/cups/pairs/create_form.rb
new file mode 100644
index 00000000..5c68ee0e
--- /dev/null
+++ b/app/forms/cups/pairs/create_form.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Cups
+ module Pairs
+ class CreateForm
+ include Deps[validator: 'validators.cups.pair']
+
+ def call(cups_round:, params:)
+ errors = validator.call(params: params)
+ return { errors: errors } if errors.any?
+
+ result = cups_round.cups_pairs.create!(params)
+
+ { result: result }
+ end
+ end
+ end
+end
diff --git a/app/forms/cups/pairs/update_form.rb b/app/forms/cups/pairs/update_form.rb
new file mode 100644
index 00000000..75915fea
--- /dev/null
+++ b/app/forms/cups/pairs/update_form.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Cups
+ module Pairs
+ class UpdateForm
+ include Deps[validator: 'validators.cups.pair']
+
+ def call(cups_pair:, params:)
+ errors = validator.call(params: params)
+ return { errors: errors } if errors.any?
+
+ cups_pair.update!(params)
+
+ { result: cups_pair.reload }
+ end
+ end
+ end
+end
diff --git a/app/validators/cups/pair_validator.rb b/app/validators/cups/pair_validator.rb
new file mode 100644
index 00000000..f6fba2da
--- /dev/null
+++ b/app/validators/cups/pair_validator.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Cups
+ class PairValidator < ApplicationValidator
+ include Deps[contract: 'contracts.cups.pair']
+ end
+end
diff --git a/app/views/controllers/admin/cups/pairs/_form.html.erb b/app/views/controllers/admin/cups/pairs/_form.html.erb
new file mode 100644
index 00000000..55b28e23
--- /dev/null
+++ b/app/views/controllers/admin/cups/pairs/_form.html.erb
@@ -0,0 +1,16 @@
+
+ <%= form.label :home_name, t('views.admin.cups.pairs.index.home_name'), class: 'form-label' %>
+ <%= form.text_field :home_name, value: @cups_pair.home_name, class: 'form-value' %>
+
+
+ <%= form.label :visitor_name, t('views.admin.cups.pairs.index.visitor_name'), class: 'form-label' %>
+ <%= form.text_field :visitor_name, value: @cups_pair.visitor_name, class: 'form-value' %>
+
+
+ <%= form.label :start_at, t('views.admin.cups.pairs.index.start_at'), class: 'form-label' %>
+ <%= form.text_field :start_at, value: @cups_pair.start_at&.strftime('%Y-%m-%d %H:%M'), placeholder: '2024-01-31 12:13', class: 'form-value' %>
+
+
+ <%= form.label :points, t('views.admin.cups.pairs.index.points'), class: 'form-label' %>
+ <%= form.text_field :points, value: @cups_pair.points&.join('-'), placeholder: '2-1', class: 'form-value' %>
+
diff --git a/app/views/controllers/admin/cups/pairs/edit.html.erb b/app/views/controllers/admin/cups/pairs/edit.html.erb
new file mode 100644
index 00000000..30e807d1
--- /dev/null
+++ b/app/views/controllers/admin/cups/pairs/edit.html.erb
@@ -0,0 +1,7 @@
+<%= t('views.admin.cups.pairs.edit.title') %>
+
+ <%= form_with model: @cups_pair, url: admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: params[:id]), method: :patch do |form| %>
+ <%= render 'form', form: form %>
+ <%= form.submit t('views.admin.cups.pairs.edit.update'), class: 'btn-primary' %>
+ <% end %>
+
diff --git a/app/views/controllers/admin/cups/pairs/index.html.erb b/app/views/controllers/admin/cups/pairs/index.html.erb
new file mode 100644
index 00000000..f5015359
--- /dev/null
+++ b/app/views/controllers/admin/cups/pairs/index.html.erb
@@ -0,0 +1,30 @@
+
+
<%= t('views.admin.cups.pairs.index.header') %>
+ <%= link_to t('views.admin.cups.pairs.index.new_cups_pair'), new_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id]), class: 'btn-primary' %>
+
+
+
+
+ ID |
+ <%= t('views.admin.cups.pairs.index.home_name') %> |
+ <%= t('views.admin.cups.pairs.index.visitor_name') %> |
+ <%= t('views.admin.cups.pairs.index.start_at') %> |
+ <%= t('views.admin.cups.pairs.index.points') %> |
+ |
+
+
+
+ <% @cups_pairs.each do |cups_pair| %>
+
+ <%= cups_pair[:id] %> |
+ <%= cups_pair[:home_name] %> |
+ <%= cups_pair[:visitor_name] %> |
+ <%= cups_pair[:start_at] %> |
+ <%= cups_pair[:points]&.join('-') %> |
+
+ <%= link_to t('views.admin.cups.pairs.index.edit'), edit_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: cups_pair[:id]), class: 'btn-primary btn-small' %>
+ |
+
+ <% end %>
+
+
diff --git a/app/views/controllers/admin/cups/pairs/new.html.erb b/app/views/controllers/admin/cups/pairs/new.html.erb
new file mode 100644
index 00000000..d5e9ed92
--- /dev/null
+++ b/app/views/controllers/admin/cups/pairs/new.html.erb
@@ -0,0 +1,7 @@
+<%= t('views.admin.cups.pairs.index.new_cups_pair') %>
+
+ <%= form_with model: @cups_pair, url: admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]), method: :post do |form| %>
+ <%= render 'form', form: form %>
+ <%= form.submit t('views.admin.cups.pairs.new.create'), class: 'btn-primary' %>
+ <% end %>
+
diff --git a/app/views/controllers/admin/cups/rounds/index.html.erb b/app/views/controllers/admin/cups/rounds/index.html.erb
index 62262b0a..b4ee0f2a 100644
--- a/app/views/controllers/admin/cups/rounds/index.html.erb
+++ b/app/views/controllers/admin/cups/rounds/index.html.erb
@@ -18,7 +18,7 @@
<%= cups_round[:name] %> |
<%= cups_round[:position] %> |
-
+ <%= link_to t('views.admin.cups.rounds.index.pairs'), admin_cups_round_pairs_path(cups_round_id: cups_round[:id]), class: 'btn-primary btn-small' %>
|
<% end %>
diff --git a/config/initializers/container.rb b/config/initializers/container.rb
index 8a5a30f7..2ef31c3d 100644
--- a/config/initializers/container.rb
+++ b/config/initializers/container.rb
@@ -44,6 +44,7 @@ def register(key)
register('contracts.oracul') { OraculContract.new }
register('contracts.cup') { CupContract.new }
register('contracts.cups.round') { Cups::RoundContract.new }
+ register('contracts.cups.pair') { Cups::PairContract.new }
# validators
register('validators.games.create') { Games::CreateValidator.new }
@@ -68,6 +69,7 @@ def register(key)
register('validators.oracul') { OraculValidator.new }
register('validators.cup') { CupValidator.new }
register('validators.cups.round') { Cups::RoundValidator.new }
+ register('validators.cups.pair') { Cups::PairValidator.new }
# forms
register('forms.teams.players.create') { Teams::Players::CreateForm.new }
@@ -93,6 +95,8 @@ def register(key)
register('forms.oraculs.forecasts.update') { Oraculs::Forecasts::UpdateForm.new }
register('forms.cups.create') { Cups::CreateForm.new }
register('forms.cups.rounds.create') { Cups::Rounds::CreateForm.new }
+ register('forms.cups.pairs.create') { Cups::Pairs::CreateForm.new }
+ register('forms.cups.pairs.update') { Cups::Pairs::UpdateForm.new }
# notifiers
register('notifiers.telegram.user.deadline_report_payload') { Telegram::User::DeadlineReportPayload.new }
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e569cdd4..ab0805f5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -65,6 +65,11 @@ en:
rounds:
create:
success: Cups round is created
+ pairs:
+ create:
+ success: Cups pair is created
+ update:
+ success: Cups pair is updated
seasons:
create:
success: Season is created
@@ -126,6 +131,9 @@ en:
permission: Email is not confirmed yet
ban: Account is banned
services:
+ cups:
+ create:
+ league_does_not_exist: League does not exist
oraculs:
create:
not_unique: Oracul already exists
@@ -186,8 +194,23 @@ en:
new_cups_round: New cups round
name: Name
position: Position
+ pairs: Games
new:
create: Create new cups round
+ pairs:
+ index:
+ header: Round games
+ new_cups_pair: New round game
+ home_name: Home name
+ visitor_name: Visitor name
+ start_at: Start at
+ points: Points
+ edit: Edit
+ new:
+ create: Create new pair
+ edit:
+ title: Editing pair
+ update: Update pair
weeks:
index:
header: Weeks
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 33b4edcb..0c6c038f 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -65,6 +65,11 @@ ru:
rounds:
create:
success: Кубковый раунд создан
+ pairs:
+ create:
+ success: Кубковая игры создана
+ update:
+ success: Кубковая игры обновлена
seasons:
create:
success: Сезон создан
@@ -126,6 +131,9 @@ ru:
permission: Необходимо подтвердить электронную почту
ban: Аккаунт заблокирован
services:
+ cups:
+ create:
+ league_does_not_exist: Лига не существует
oraculs:
create:
not_unique: Оракл уже создан
@@ -186,8 +194,23 @@ ru:
new_cups_round: Новый кубковый раунд
name: Название
position: Положение
+ pairs: Игры
new:
create: Создать новый раунд
+ pairs:
+ index:
+ header: Игры раунда
+ new_cups_pair: Новая игры
+ home_name: Первое название
+ visitor_name: Второе название
+ start_at: Время начала
+ points: Результат
+ edit: Изменить
+ new:
+ create: Создать новую игру
+ edit:
+ title: Редактирование игры
+ update: Обновить игру
weeks:
index:
header: Игровые недели
diff --git a/config/routes.rb b/config/routes.rb
index a26fe420..41ccbd3f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -32,6 +32,11 @@
resources :rounds, only: %i[index new create]
end
end
+ resources :cups_rounds, only: %i[] do
+ scope module: :cups do
+ resources :pairs, only: %i[index new edit create update]
+ end
+ end
resources :weeks, only: %i[index edit update]
end
diff --git a/spec/controllers/admin/cups/pairs_controller_spec.rb b/spec/controllers/admin/cups/pairs_controller_spec.rb
new file mode 100644
index 00000000..e88a88c3
--- /dev/null
+++ b/spec/controllers/admin/cups/pairs_controller_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+describe Admin::Cups::PairsController do
+ let!(:cup) { create :cup }
+ let!(:cups_round) { create :cups_round, cup: cup }
+
+ describe 'GET#index' do
+ it_behaves_like 'required auth'
+ it_behaves_like 'required email confirmation'
+ it_behaves_like 'required available email'
+ it_behaves_like 'required admin'
+
+ context 'for admin' do
+ sign_in_admin
+
+ context 'for authorized user' do
+ before { create :cups_pair, cups_round: cups_round }
+
+ it 'renders index page' do
+ do_request
+
+ expect(response).to render_template :index
+ end
+ end
+ end
+
+ def do_request
+ get :index, params: { cups_round_id: cups_round.id, locale: 'en' }
+ end
+ end
+
+ describe 'GET#new' do
+ it_behaves_like 'required auth'
+ it_behaves_like 'required email confirmation'
+ it_behaves_like 'required available email'
+ it_behaves_like 'required admin'
+
+ context 'for admin' do
+ sign_in_admin
+
+ context 'for unexisting cups round' do
+ it 'render not found page' do
+ do_request
+
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for existing cups round' do
+ it 'renders new page' do
+ get :new, params: { cups_round_id: cups_round.id, locale: 'en' }
+
+ expect(response).to render_template :new
+ end
+ end
+ end
+
+ def do_request
+ get :new, params: { cups_round_id: 'unexisting', locale: 'en' }
+ end
+ end
+
+ describe 'GET#edit' do
+ it_behaves_like 'required auth'
+ it_behaves_like 'required email confirmation'
+ it_behaves_like 'required available email'
+ it_behaves_like 'required admin'
+
+ context 'for admin' do
+ sign_in_admin
+
+ context 'for unexisting cups round' do
+ it 'render not found page' do
+ do_request
+
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for unexisting cups pair' do
+ it 'render not found page' do
+ get :edit, params: { cups_round_id: cups_round.id, id: 'unexisting', locale: 'en' }
+
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for existing cups round and pair' do
+ let!(:cups_pair) { create :cups_pair, cups_round: cups_round }
+
+ it 'renders edit page' do
+ get :edit, params: { cups_round_id: cups_round.id, id: cups_pair, locale: 'en' }
+
+ expect(response).to render_template :edit
+ end
+ end
+ end
+
+ def do_request
+ get :edit, params: { cups_round_id: 'unexisting', id: 'unexisting', locale: 'en' }
+ end
+ end
+
+ describe 'POST#create' do
+ it_behaves_like 'required auth'
+ it_behaves_like 'required email confirmation'
+ it_behaves_like 'required available email'
+ it_behaves_like 'required admin'
+
+ context 'for admin' do
+ sign_in_admin
+
+ context 'for unexisting cups round' do
+ it 'does not create cups pair', :aggregate_failures do
+ expect { do_request }.not_to change(Cups::Pair, :count)
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for existing cups round' do
+ let(:request) {
+ post :create, params: {
+ cups_round_id: cups_round.id, cups_pair: { home_name: 'Home', visitor_name: 'Visitor' }
+ }
+ }
+
+ it 'creates cups pair', :aggregate_failures do
+ expect { request }.to change(cups_round.cups_pairs, :count).by(1)
+ expect(response).to redirect_to admin_cups_round_pairs_path(cups_round_id: cups_round.id)
+ end
+ end
+ end
+
+ def do_request
+ post :create, params: { cups_round_id: 'unexisting', cups_pair: { home_name: 'Home', visitor_name: 'Visitor' } }
+ end
+ end
+
+ describe 'PATCH#update' do
+ it_behaves_like 'required auth'
+ it_behaves_like 'required email confirmation'
+ it_behaves_like 'required available email'
+ it_behaves_like 'required admin'
+
+ context 'for admin' do
+ sign_in_admin
+
+ context 'for unexisting cups round' do
+ it 'redirects to not found page' do
+ do_request
+
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for unexisting cups pair' do
+ it 'redirects to not found page' do
+ patch :update, params: { cups_round_id: cups_round.id, id: 'unexisting', cups_pair: { home_name: 'Home' } }
+
+ expect(response).to render_template 'shared/404'
+ end
+ end
+
+ context 'for existing cups round' do
+ let!(:cups_pair) { create :cups_pair, cups_round: cups_round, home_name: 'Old home', points: [3, 1] }
+ let(:request) {
+ patch :update, params: {
+ cups_round_id: cups_round.id, id: cups_pair.id, cups_pair: { home_name: 'Home', points: '2-1' }
+ }
+ }
+
+ it 'updates cups pair', :aggregate_failures do
+ request
+
+ expect(cups_pair.reload.home_name).to eq 'Home'
+ expect(cups_pair.reload.points).to eq([2, 1])
+ expect(response).to redirect_to admin_cups_round_pairs_path(cups_round_id: cups_round.id)
+ end
+ end
+ end
+
+ def do_request
+ patch :update, params: {
+ cups_round_id: 'unexisting', id: 'unexisting', cups_pair: { home_name: 'Home', visitor_name: 'Visitor' }
+ }
+ end
+ end
+end