Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose more data over the API #143

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/controllers/accounts/requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 12 additions & 2 deletions app/controllers/contests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'builder'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be used?

Suggested change
require 'builder'


class ContestsController < ApplicationController
def permitted_params
@_permitted_params ||= begin
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
9 changes: 6 additions & 3 deletions app/controllers/groups_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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|
Expand Down
6 changes: 4 additions & 2 deletions app/controllers/problems_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def index

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @problems }
end
end

Expand All @@ -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?
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
36 changes: 35 additions & 1 deletion app/models/contest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -166,4 +166,38 @@ def max_extra_time
(duration*3600).to_i
end

def to_xml(opts={})
opts[:exclude] ||= [:startcode]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
opts[:exclude] ||= [:startcode]
opts[:except] ||= [: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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if policy.scoreboard? then
if policy.show_details? then

Problem IDs should be hidden to users unless the contest has ended or they are a current/past contestant (we may want to reuse problems). We should probably also restrict access to :problem_set_id in the same way.

# 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
8 changes: 8 additions & 0 deletions app/models/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

22 changes: 21 additions & 1 deletion app/models/problem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Comment on lines +117 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we restrict these to admins? This will list upcoming/current contests and groups/problem sets that the user doesn't have access to.


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
10 changes: 10 additions & 0 deletions app/models/problem_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +44 to +45
Copy link
Member

@Holmes98 Holmes98 Feb 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with Problem#to_xml, should we restrict these to admins?

XmlUtil.serialize_id_list xml, 'problems', problems
end
end
end
16 changes: 15 additions & 1 deletion app/models/request.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.`
Comment on lines +48 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opts[:except] should work =)

self.expired_at = 0
end

super(opts)
end
end
10 changes: 8 additions & 2 deletions app/models/submission.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Submission < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection

belongs_to :user
belongs_to :problem
has_many :contest_scores
Expand All @@ -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?
Expand Down Expand Up @@ -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

25 changes: 25 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
21 changes: 21 additions & 0 deletions app/services/xml_util.rb
Original file line number Diff line number Diff line change
@@ -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