From 62bc78274ad5287f3ccde2ec234bbf88633d85a7 Mon Sep 17 00:00:00 2001 From: Absurdon Date: Sat, 7 Oct 2017 12:28:06 +0200 Subject: [PATCH] Creating view to show confirmed matches, fixing bugs in permission logic and making proposal status updates ajax requests --- app/controllers/contests_controller.rb | 6 ++- app/controllers/match_proposals_controller.rb | 47 ++++++++++++------- app/models/match_proposal.rb | 21 ++++++--- app/views/contests/confirmed_matches.html.erb | 13 +++++ app/views/match_proposals/index.html.erb | 30 +++++++++--- app/views/matches/show.html.erb | 2 +- config/routes.rb | 2 + 7 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 app/views/contests/confirmed_matches.html.erb diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 1b6731b..d2157ef 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -1,5 +1,5 @@ class ContestsController < ApplicationController - before_filter :get_contest, only: [:show, :edit, :update, :destroy, :del_map, :scores, :recalc] + before_filter :get_contest, only: [:show, :edit, :update, :destroy, :del_map, :scores, :recalc, :confirmed_matches] def index # @contests = Contest.all @@ -102,6 +102,10 @@ class ContestsController < ApplicationController redirect_to contests_url end + def confirmed_matches + @match_props = MatchProposal.confirmed_for_contest(@contest) + end + private def get_contest diff --git a/app/controllers/match_proposals_controller.rb b/app/controllers/match_proposals_controller.rb index 98a7e53..ae042db 100644 --- a/app/controllers/match_proposals_controller.rb +++ b/app/controllers/match_proposals_controller.rb @@ -1,6 +1,7 @@ class MatchProposalsController < ApplicationController before_filter :get_match def index + raise AccessError unless @match.user_in_match?(cuser) end def new @@ -25,25 +26,35 @@ class MatchProposalsController < ApplicationController end def update - @proposal = MatchProposal.find(params[:id]) - raise AccessError unless @proposal.can_update?(cuser, params[:match_proposal]) - @proposal.status = params[:match_proposal][:status] - if @proposal.save - # TODO: rework messages - # TODO: make it so only one proposal can be confirmed for a match at any given time - action = case @proposal.status - when MatchProposal::STATUS_CONFIRMED - "Confirmed Proposal for #{Time.use_zone(view_context.timezone_offset) { @proposal.proposed_time.strftime('%d %B %y %H:%M %Z') }}" - when MatchProposal::STATUS_REJECTED - "Rejected Proposal for #{Time.use_zone(view_context.timezone_offset) { @proposal.proposed_time.strftime('%d %B %y %H:%M %Z') }}" - else - "Smthn went wrong" - end - flash[:notice] = action - else - flash[:notice] = "Error" + raise AccessError unless request.xhr? # Only respond to ajax requests + rjson = {} + proposal = MatchProposal.find(params[:id]) + unless proposal + rjson[:error] = { + code: 404, + message: "No proposal with id #{params[:id]}" + } + render(json: rjson, status: :not_found) && return + end + unless proposal.can_update?(cuser, params[:match_proposal]) + rjson[:error] = { + code: 403, + message: "You are not allowed to update the state to #{MatchProposal.status_strings[params[:match_proposal][:status].to_i]}" + } + render(json: rjson, status: :forbidden) && return + end + proposal.status = params[:match_proposal][:status] + if proposal.save + rjson[:status] = MatchProposal.status_strings[proposal.status] + rjson[:message] = "Successfully updated status to #{MatchProposal.status_strings[proposal.status]}" + render(json: rjson, status: :success) + else + rjson[:error] = { + code: 500, + message: 'Something went wrong! Please try again.' + } + render(json: rjson, status: 500) end - redirect_to(match_proposals_path(@match)) end private diff --git a/app/models/match_proposal.rb b/app/models/match_proposal.rb index 79c0a0e..4d74e54 100644 --- a/app/models/match_proposal.rb +++ b/app/models/match_proposal.rb @@ -19,8 +19,10 @@ class MatchProposal < ActiveRecord::Base scope :of_match, ->(match) { where('match_id = ?', match.id) } scope :confirmed_for_match, ->(match) { where('match_id = ? AND status = ?', match.id, STATUS_CONFIRMED) } scope :confirmed_upcoming, ->{ where('status = ? AND proposed_time > UTC_TIMESTAMP()', STATUS_CONFIRMED) } + scope :confirmed_for_contest, + ->(contest){ includes(:match).where(matches:{contest_id: contest.id}, status: STATUS_CONFIRMED).all} - def status_strings + def self.status_strings {STATUS_PENDING => 'Pending', STATUS_REVOKED => 'Revoked', STATUS_REJECTED => 'Rejected', @@ -47,7 +49,11 @@ class MatchProposal < ActiveRecord::Base cuser && cuser.admin? end - private + def state_immutable? + status == STATUS_REJECTED || + status == STATUS_DELAYED || + status == STATUS_REVOKED + end def status_change_allowed?(cuser, new_status) case new_status @@ -58,17 +64,20 @@ class MatchProposal < ActiveRecord::Base # only confirmed matches can be set to delayed # only admins can set matches to delayed and only if they are not playing in that match # matches can only be delayed if they are not to far in the future - return false unless self.status == STATUS_CONFIRMED && cuser.admin? && + return self.status == STATUS_CONFIRMED && cuser.admin? && !self.match.user_in_match?(cuser) && self.proposed_time <= CONFIRMATION_LIMIT.minutes.from_now when STATUS_REVOKED # unconfirmed can only be revoked by team making the proposal # confirmed can only be revoked if soon enough before match time - return false unless self.status == STATUS_PENDING && self.team == cuser.team || + return self.status == STATUS_PENDING && self.team == cuser.team || self.status == STATUS_CONFIRMED && self.proposed_time > CONFIRMATION_LIMIT.minutes.from_now when STATUS_CONFIRMED, STATUS_REJECTED # only team proposed to can reject or confirm and only if soon enough before match time - return false unless self.status == STATUS_PENDING && self.team != cuser.team && - self.proposed_time < CONFIRMATION_LIMIT.minutes.from_now + status_ok = self.status == STATUS_PENDING + team_ok = self.team != cuser.team + time_ok = CONFIRMATION_LIMIT.minutes.from_now < self.proposed_time + + return status_ok && team_ok && time_ok else # invalid status return false diff --git a/app/views/contests/confirmed_matches.html.erb b/app/views/contests/confirmed_matches.html.erb new file mode 100644 index 0000000..be00048 --- /dev/null +++ b/app/views/contests/confirmed_matches.html.erb @@ -0,0 +1,13 @@ +

Confirmed Matches for <%= @contest.name %>

+ + + + + + <% @match_props && @match_props.each do |mp| %> + + + + + <% end %> +
MatchScheduled for
<%= link_to mp.match.contester1, contester_path(mp.match.contester1) %> VS <%= link_to mp.match.contester2, contester_path(mp.match.contester2) %><%= longertime(mp.proposed_time) %>
\ No newline at end of file diff --git a/app/views/match_proposals/index.html.erb b/app/views/match_proposals/index.html.erb index f32b35f..d000b2f 100644 --- a/app/views/match_proposals/index.html.erb +++ b/app/views/match_proposals/index.html.erb @@ -1,7 +1,8 @@

Proposals

+

<%= @match.contester1 %> VS <%= @match.contester2 %>

<%= link_to 'Back', match_path(@match), class: 'button' %> <% if @match.match_proposals.empty? %> -

There are no proposals yet

+

There are no proposals yet

<% else %> @@ -16,13 +17,19 @@ - + <% if @match.can_make_proposal?(cuser) %> <% end %> @@ -33,7 +40,16 @@ function proposalStateSubmit(newState, formID) { var form = $('#edit_match_proposal_' + formID); form.children("input[type='hidden']").val(newState); - form.submit(); + $.post(form.attr('action'),form.serialize(), function(data) { + datajson = data.responseText; + tr = form.closest('tr'); + tr.children('td').eq(1).html(datajson.status); + alert(datajson.message); + }, 'json') + .error(function (err) { + errjson = JSON.parse(err.responseText); + alert(errjson.error.message); + }); } <% end %> diff --git a/app/views/matches/show.html.erb b/app/views/matches/show.html.erb index b991e79..61e394c 100755 --- a/app/views/matches/show.html.erb +++ b/app/views/matches/show.html.erb @@ -116,7 +116,7 @@

<%= @match.report.html_safe %>

<% end %> - <% if cuser and (cuser.admin? or @match.of_user cuser) %> + <% if cuser and (cuser.admin? or @match.user_in_match? cuser) %>
<%= link_to 'Proposals', match_proposals_path(@match), class: 'button' %>
diff --git a/config/routes.rb b/config/routes.rb index fde6255..399be2c 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,6 +51,8 @@ Ensl::Application.routes.draw do resources :users resources :locks resources :contesters + + get "contests/:id/confirmedmatches" => "contests#confirmed_matches", as: :confirmed_matches resources :contests resources :challenges resources :servers
<%= proposal.team.name %> <%= longtime proposal.proposed_time %> <%= proposal.status_strings[proposal.status] %><%= MatchProposal.status_strings[proposal.status] %> - <%= form_for proposal, url: match_proposal_path(@match, proposal) do |f| %> - <%= f.hidden_field :status, value: 0 %> - <%= link_to_function icon('check'), "proposalStateSubmit(#{MatchProposal::STATUS_CONFIRMED},#{proposal.id})" %> - <%= link_to_function icon('times'), "proposalStateSubmit(#{MatchProposal::STATUS_REJECTED},#{proposal.id})" %> + <% unless proposal.state_immutable? %> + <%= form_for proposal, url: match_proposal_path(@match, proposal) do |f| %> + <%= f.hidden_field :status, value: 0 %> + <%= link_to_function icon('check'), "proposalStateSubmit(#{MatchProposal::STATUS_CONFIRMED},#{proposal.id})", title: 'Confirm' %> + <%= link_to_function icon('times'), "proposalStateSubmit(#{MatchProposal::STATUS_REJECTED},#{proposal.id})", title: 'Reject' %> + <%= link_to_function icon('eraser'), "proposalStateSubmit(#{MatchProposal::STATUS_REVOKED},#{proposal.id})", title: 'Revoke' %> + <% if proposal.status_change_allowed?(cuser, MatchProposal::STATUS_DELAYED) %> + <%= link_to_function icon('hourglass'), "proposalStateSubmit(#{MatchProposal::STATUS_DELAYED},#{proposal.id})", title: 'Delay' %> + <% end %> + <% end %> <% end %>