class SubmissionsController < ApplicationController before_action :authorize_request, only: [:index, :destroy] before_action :check_maintenance, only: [:create, :destroy] before_action :check_wait, only: [:create] # Wait in batch_create is not allowed before_action :check_batched_submissions, only: [:batch_create, :batch_show] before_action :check_queue_size, only: [:create, :batch_create] before_action :check_requested_fields, except: [:batch_create] # Fields are ignored in batch_create before_action :set_base64_encoded def index page = params[:page].try(:to_i) || 1 per_page = params[:per_page].try(:to_i) || Submission.per_page if page <= 0 render json: { error: "invalid page: #{page}" }, status: :bad_request return elsif per_page < 0 render json: { error: "invalid per_page: #{per_page}" }, status: :bad_request return end submissions = Submission.paginate(page: page, per_page: per_page) serializable_submissions = ActiveModelSerializers::SerializableResource.new( submissions, { each_serializer: SubmissionSerializer, base64_encoded: @base64_encoded, fields: @requested_fields } ) render json: { submissions: serializable_submissions.as_json, meta: pagination_dict(submissions) } rescue Encoding::UndefinedConversionError => e render json: { error: "some attributes for one or more submissions cannot be converted to UTF-8, use base64_encoded=true query parameter" }, status: :bad_request end def destroy if !Config::ENABLE_SUBMISSION_DELETE render json: { error: "delete not allowed" }, status: :bad_request return end submission = Submission.find_by!(token: params[:token]) if submission.status == Status.queue || submission.status == Status.process render json: { error: "submission cannot be deleted because its status is #{submission.status.id} (#{submission.status.name})" }, status: :bad_request return end submission.delete # Forcing base64_encoded=true because it guarantees user will get requested data after delete. render json: submission, base64_encoded: true, fields: @requested_fields end def show token = params[:token] render json: Rails.cache.fetch("#{token}", expires_in: Config::SUBMISSION_CACHE_DURATION, race_condition_ttl: 0.1*Config::SUBMISSION_CACHE_DURATION) { Submission.find_by!(token: token) }, base64_encoded: @base64_encoded, fields: @requested_fields rescue Encoding::UndefinedConversionError render_conversion_error(:bad_request) end def batch_show tokens = (request.headers[:tokens] || params[:tokens]).to_s.strip.split(",") if tokens.length > Config::MAX_SUBMISSION_BATCH_SIZE render json: { error: "number of submissions in a batch should be less than or equal to #{Config::MAX_SUBMISSION_BATCH_SIZE}" }, status: :bad_request return elsif tokens.length == 0 render json: { error: "there should be at least one submission in a batch" }, status: :bad_request return end existing_submissions = Hash[Submission.where(token: tokens).collect{ |s| [s.token, s] }] submissions = [] tokens.each do |token| if existing_submissions.has_key?(token) serialized_submission = ActiveModelSerializers::SerializableResource.new( existing_submissions[token], { serializer: SubmissionSerializer, base64_encoded: @base64_encoded, fields: @requested_fields } ) submissions << serialized_submission.as_json else submissions << nil end end render json: { submissions: submissions } rescue Encoding::UndefinedConversionError => e render json: { error: "some attributes for one or more submissions cannot be converted to UTF-8, use base64_encoded=true query parameter" }, status: :bad_request end def create submission = Submission.new(submission_params(params)) if submission.save if @wait begin IsolateRunner.perform_now(submission) submission.reload render json: submission, status: :created, base64_encoded: @base64_encoded, fields: @requested_fields rescue Encoding::UndefinedConversionError => e render_conversion_error(:created, submission.token) end else IsolateRunner.perform_later(submission) render json: submission, status: :created, fields: [:token] end else render json: submission.errors, status: :unprocessable_entity end end # Batch Create does not support sync (wait=true) mode. def batch_create number_of_submissions = params[:submissions].try(:size).to_i if number_of_submissions > Config::MAX_SUBMISSION_BATCH_SIZE render json: { error: "number of submissions in a batch should be less than or equal to #{Config::MAX_SUBMISSION_BATCH_SIZE}" }, status: :bad_request return elsif number_of_submissions == 0 render json: { error: "there should be at least one submission in a batch" }, status: :bad_request return end submissions = params[:submissions].each.collect{ |p| Submission.new(submission_params(p)) } response = [] has_valid_submission = false submissions.each do |submission| if submission.save IsolateRunner.perform_later(submission) response << { token: submission.token } has_valid_submission = true else response << submission.errors end end render json: response, status: has_valid_submission ? :created : :unprocessable_entity end private def submission_params(params) submission_params = params.permit( :source_code, :language_id, :compiler_options, :command_line_arguments, :number_of_runs, :stdin, :expected_output, :cpu_time_limit, :cpu_extra_time, :wall_time_limit, :memory_limit, :stack_limit, :max_processes_and_or_threads, :enable_per_process_and_thread_time_limit, :enable_per_process_and_thread_memory_limit, :max_file_size, :redirect_stderr_to_stdout, :callback_url, :additional_files, :enable_network ) submission_params[:additional_files] = Base64Service.decode(submission_params[:additional_files]) if @base64_encoded submission_params[:source_code] = Base64Service.decode(submission_params[:source_code]) submission_params[:stdin] = Base64Service.decode(submission_params[:stdin]) submission_params[:expected_output] = Base64Service.decode(submission_params[:expected_output]) end submission_params end def check_wait @wait = params[:wait] == "true" if @wait && !Config::ENABLE_WAIT_RESULT render json: { error: "wait not allowed" }, status: :bad_request end end def check_batched_submissions unless Config::ENABLE_BATCHED_SUBMISSIONS render json: { error: "batched submissions are not allowed" }, status: :bad_request end end def check_queue_size number_of_submissions = params[:submissions].try(:size).presence || 1 if Resque.size(ENV["JUDGE0_VERSION"]) + number_of_submissions > Config::MAX_QUEUE_SIZE render json: { error: "queue is full" }, status: :service_unavailable end end def check_requested_fields fields_service = Fields::Submission.new(params[:fields]) render json: { error: "invalid fields: [#{fields_service.invalid_fields.join(", ")}]" }, status: :bad_request if fields_service.has_invalid_fields? @requested_fields = fields_service.requested_fields end def set_base64_encoded if Config::DISABLE_IMPLICIT_BASE64_ENCODING @base64_encoded = params[:base64_encoded] == "true" else @base64_encoded = params[:base64_encoded] != "false" end end def render_conversion_error(status, token = nil) response_json = { error: "some attributes for this submission cannot be converted to UTF-8, use base64_encoded=true query parameter", } response_json[:token] = token if token render json: response_json, status: status end end