diff --git a/app/controllers/accounts/requests_controller.rb b/app/controllers/accounts/requests_controller.rb index 642f9366..f25101ab 100644 --- a/app/controllers/accounts/requests_controller.rb +++ b/app/controllers/accounts/requests_controller.rb @@ -6,5 +6,16 @@ class Accounts::RequestsController < ApplicationController def index @pending_requests = current_user.requests.pending @requests = current_user.requests.closed + + respond_to do |format| + format.html + + # Return all requests in the XML, sorted by creation date + format.xml { + render :xml => (@pending_requests + @requests).sort do |a, b| + a.created_at <=> b.created_at + end + } + end end end diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 3d8a37e0..68e239d9 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -1,3 +1,5 @@ +require 'builder' + class ContestsController < ApplicationController def permitted_params @_permitted_params ||= begin @@ -18,7 +20,7 @@ def index authorize Contest.new, :manage? @contests = Contest.order("end_time DESC") end - + respond_to do |format| format.html # index.html.erb end @@ -42,6 +44,11 @@ def browse else raise Pundit::NotAuthorizedError end + + respond_to do |format| + format.html + format.xml { render :xml => @contests.to_xml(:user => current_user) } + end end # GET /contests/1 @@ -61,7 +68,10 @@ def show @contest_message = "You have not started this contest." end - render :layout => 'contest' + respond_to do |format| + format.html { render :layout => 'contest' } + format.xml { render :xml => @contest.to_xml(:user => current_user) } + end end def info diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 6165b184..0c27db5d 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -59,7 +59,10 @@ def show @group = Group.find(params[:id]) if policy(@group).access? @problem_set_associations = @group.problem_set_associations - render :layout => "group" + respond_to do |format| + format.html { render :layout => "group" } + format.xml { render :xml => @group } + end else redirect_to info_group_path(@group) end @@ -73,8 +76,8 @@ def contests format.html { render :layout => "group" } end end - - def info + + def info @group = Group.find(params[:id]) authorize @group, :show? respond_to do |format| diff --git a/app/controllers/problems_controller.rb b/app/controllers/problems_controller.rb index b345ac7f..1440a9cc 100644 --- a/app/controllers/problems_controller.rb +++ b/app/controllers/problems_controller.rb @@ -50,6 +50,7 @@ def index respond_to do |format| format.html # index.html.erb + format.xml { render :xml => @problems } end end @@ -73,6 +74,7 @@ def show respond_to do |format| format.html { render :layout => "problem" } + format.xml { render :xml => @problem } end if user_signed_in? @@ -115,7 +117,7 @@ def submissions start_time = current_user.contest_relations.joins(:contest => {:problem_set => :problems}).where{(started_at <= DateTime.now) & (finish_at > DateTime.now) & (contest.problem_set.problems.id == my{params[:id]})}.minimum(:started_at) @submissions = @problem.submission_history(current_user,start_time) end - + respond_to do |format| format.html { render :layout => "problem" } end @@ -225,7 +227,7 @@ def create def update @problem = Problem.find(params[:id]) authorize @problem, :update? - + @problem.assign_attributes(permitted_params) respond_to do |format| if validate(@problem) && @problem.save diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index d8de9ddf..de2bea53 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -21,13 +21,13 @@ def visible_attributes def show @user = User.find(params[:id]) authorize @user, :show? - @solved_problems = @user.user_problem_relations.where(ranked_score: 100).joins(:problem).select([:problem_id, :ranked_submission_id, {problem: :name}]).order("problems.name") + @solved_problems = @user.solved_problems @user_presenter = UserPresenter.new(@user).permit!(*visible_attributes) respond_to do |format| format.html - format.xml {render :xml => @user } + format.xml {render :xml => @user, :user => current_user } end end diff --git a/app/models/contest.rb b/app/models/contest.rb index 9edd6d23..332f02be 100644 --- a/app/models/contest.rb +++ b/app/models/contest.rb @@ -117,7 +117,7 @@ def status return "Running" if is_running? return finalized? ? "Finalized" : "Preliminary" end - + def status_text(user_id) return "The contest has ended." if ended? @@ -166,4 +166,38 @@ def max_extra_time (duration*3600).to_i end + def to_xml(opts={}) + opts[:exclude] ||= [:startcode] + + super(opts) do |xml| + if opts[:user] then + policy = Pundit.policy(opts[:user], self) + + has_contestants = policy.contestants? + has_scoreboard = policy.scoreboard? + + if policy.contestants? then + XmlUtil.serialize_id_list xml, 'contestants', contestants + end + + if policy.scoreboard? then + # TODO: Include scoreboard info + + XmlUtil.serialize_list xml, 'problems', problem_set.problems do |problem| + association = problem_associations.find {|i| i.problem_id == problem.id} + xml.id( + problem.id, + 'type' => ActiveSupport::XmlMini::TYPE_NAMES[association.weighting.class.name], + 'weighting' => association.weighting, + ) + end + end + + if policy.manage? then + XmlUtil.serialize_id_list xml, 'groups', groups + XmlUtil.serialize_id_list xml, 'registrants', registrants + end + end + end + end end diff --git a/app/models/group.rb b/app/models/group.rb index 0ac34b37..f4bcf234 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -54,5 +54,13 @@ def apply!(current_user, user = nil) def invite!(user, current_user) Request.create(:requester => current_user, :subject => self, :verb => :invite, :target => user, :requestee => user) end + + def to_xml(opts={}) + # No sensitive data + super(opts) do |xml| + XmlUtil.serialize_id_list xml, 'contests', contests + XmlUtil.serialize_id_list xml, 'problem-sets', problem_sets + end + end end diff --git a/app/models/problem.rb b/app/models/problem.rb index 9c92cfc9..67a57519 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -72,7 +72,7 @@ def get_highest_scoring_submission(user, from = DateTime.new(1), to = DateTime.n def get_score(user, from = DateTime.new(1), to = DateTime.now) subs = self.submissions.find(:all, :limit => 1, :order => "score DESC", :conditions => ["created_at between ? and ? and user_id = ?", from, to, user]) scores = subs.map {|s| s.score} - return scores.max + return scores.max end def submission_history(user, from = DateTime.new(1), to = DateTime.now) @@ -108,4 +108,24 @@ def weighted_score return nil if self.points.nil? self.points * self.weighting / self.maximum_points end + + def to_xml(opts={}) + # hide e.g. test submission stats + opts[:only] ||= [:id, :name, :statement, :input, :output, :memory_limit, :time_limit, :owner_id, :created_at, :updated_at] + + super(opts) do |xml| + XmlUtil.serialize_id_list xml, 'contests', contests + XmlUtil.serialize_id_list xml, 'groups', groups + XmlUtil.serialize_id_list xml, 'problem-sets', problem_sets + + XmlUtil.serialize_list xml, 'sample-cases', sample_cases do |sample| + xml.tag! 'sample-case' do + XmlUtil.tag xml, 'input', sample.input + XmlUtil.tag xml, 'output', sample.output + end + end + + # TODO: Possibly nice to include submission ids here if user is an admin? + end + end end diff --git a/app/models/problem_set.rb b/app/models/problem_set.rb index b1f473b5..2f274836 100644 --- a/app/models/problem_set.rb +++ b/app/models/problem_set.rb @@ -36,4 +36,14 @@ def for_group_user? u_id def total_weighting problem_associations.sum(:weighting) end + + def to_xml(opts={}) + opts[:only] ||= [:id, :name, :owner_id, :created_at, :updated_at] + + super(opts) do |xml| + XmlUtil.serialize_id_list xml, 'contests', contests + XmlUtil.serialize_id_list xml, 'groups', groups + XmlUtil.serialize_id_list xml, 'problems', problems + end + end end diff --git a/app/models/request.rb b/app/models/request.rb index b4b2e6b3..7cf3e72b 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -1,6 +1,6 @@ class Request < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection - + belongs_to :requester, :class_name => :User # entity that initiated request belongs_to :subject, :polymorphic => true # subject controlled by requester # verb # action applying subject to target @@ -42,4 +42,18 @@ def cancel! self.status = STATUS[:cancelled] self.save end + + def to_xml(opts={}) + if self.expired_at == Float::INFINITY then + # `expired_at` has the value Float::INFINITY when the request hasn't expired, + # and the XML formatter explodes when it encounters that value. Adding :expired_at + # to opts[:exclude] is the obvious solution, but for reasons unknown to me it does + # not appear to work as expected. Changing `expired_at` to some other value (which isn't + # a datetime) will result in an empty tag being emitted with a `nil="true"` attribute + # which seems like the best solution after just omitting the tag entirely.` + self.expired_at = 0 + end + + super(opts) + end end diff --git a/app/models/submission.rb b/app/models/submission.rb index 644c04ab..e80cf841 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -1,6 +1,6 @@ class Submission < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection - + belongs_to :user belongs_to :problem has_many :contest_scores @@ -9,7 +9,7 @@ class Submission < ActiveRecord::Base def user_problem_relation UserProblemRelation.where(:user_id => user_id, :problem_id => problem_id).first_or_create! end - + validates :source, :presence => true validate do |submission| errors.add :language_id, "Invalid language specified" if submission.language.nil? @@ -199,5 +199,11 @@ def update_test_messages end end + def to_xml(opts={}) + # hiding e.g. judge log + opts[:only] ||= [:id, :source, :score, :user_id, :problem_id, :created_at, :updated_at, :input, :output, :language_id, :judged_at, :evaluation] + + super(opts) + end end diff --git a/app/models/user.rb b/app/models/user.rb index c50b77c0..6e1a0456 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -173,6 +173,10 @@ def school=(value) end end + def solved_problems + self.user_problem_relations.where(ranked_score: 100).joins(:problem).select([:problem_id, :ranked_submission_id, {problem: :name}]).order("problems.name") + end + def estimated_year_level(on_this_date = DateTime.now) if school_graduation.nil? || school_graduation <= on_this_date nil @@ -187,4 +191,25 @@ def estimated_year_level(on_this_date = DateTime.now) end end + def to_xml(opts={}) + default_fields = [:id, :created_at, :updated_at, :brownie_points, :username, :avatar] + if opts[:user] then + if Pundit.policy(opts[:user], self).inspect? then + default_fields.append(:email) + default_fields.append(:name) + end + end + opts[:only] ||= default_fields + + super(opts) do |xml| + if opts[:user] then + if Pundit.policy(opts[:user], self).inspect? then + XmlUtil.serialize_id_list xml, 'groups', groups + XmlUtil.serialize_list xml, 'solved-problems', solved_problems do |association| + XmlUtil.tag xml, 'id', association.problem_id + end + end + end + end + end end diff --git a/app/services/xml_util.rb b/app/services/xml_util.rb new file mode 100644 index 00000000..d6f4b455 --- /dev/null +++ b/app/services/xml_util.rb @@ -0,0 +1,21 @@ +module XmlUtil + def self.serialize_list builder, name, docs + array_type = ActiveSupport::XmlMini::TYPE_NAMES['Array'] + builder.tag!(name, 'count' => docs.count, 'type' => array_type) do + docs.each do |doc| yield doc end + end + end + + def self.serialize_id_list builder, name, docs + array_type = ActiveSupport::XmlMini::TYPE_NAMES['Array'] + + XmlUtil.serialize_list builder, name, docs do |doc| + XmlUtil.tag builder, 'id', doc.id + end + end + + # Wrapper around ActiveSupport's to_tag method as the original is pretty long + def self.tag builder, name, value + ActiveSupport::XmlMini.to_tag(name, value, {:builder => builder}) + end +end