implementation of import wip
This commit is contained in:
1
Gemfile
1
Gemfile
@ -36,6 +36,7 @@ gem 'webpacker'
|
|||||||
gem 'rest-client'
|
gem 'rest-client'
|
||||||
gem 'rubyzip'
|
gem 'rubyzip'
|
||||||
gem 'mnemosyne-ruby'
|
gem 'mnemosyne-ruby'
|
||||||
|
gem 'proforma', path: '../proforma'
|
||||||
gem 'whenever', require: false
|
gem 'whenever', require: false
|
||||||
gem 'rails-timeago'
|
gem 'rails-timeago'
|
||||||
|
|
||||||
|
10
Gemfile.lock
10
Gemfile.lock
@ -7,6 +7,15 @@ GIT
|
|||||||
rack (>= 1.5.0)
|
rack (>= 1.5.0)
|
||||||
websocket (>= 1.1.0)
|
websocket (>= 1.1.0)
|
||||||
|
|
||||||
|
PATH
|
||||||
|
remote: ../proforma
|
||||||
|
specs:
|
||||||
|
proforma (0.1.0)
|
||||||
|
activemodel (~> 5.2.3)
|
||||||
|
activesupport (~> 5.2.3)
|
||||||
|
nokogiri (~> 1.10.2)
|
||||||
|
rubyzip (~> 1.2.2)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
@ -430,6 +439,7 @@ DEPENDENCIES
|
|||||||
nyan-cat-formatter
|
nyan-cat-formatter
|
||||||
pagedown-bootstrap-rails
|
pagedown-bootstrap-rails
|
||||||
pg
|
pg
|
||||||
|
proforma!
|
||||||
pry-byebug
|
pry-byebug
|
||||||
puma
|
puma
|
||||||
pundit
|
pundit
|
||||||
|
@ -108,27 +108,34 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def import_proforma_xml
|
def import_proforma_xml
|
||||||
begin
|
# begin
|
||||||
user = user_for_oauth2_request()
|
# user = user_for_oauth2_request
|
||||||
exercise = Exercise.new
|
# exercise = Exercise.new
|
||||||
request_body = request.body.read
|
# request_body = request.body.read # needs to be some kind of a zip file
|
||||||
exercise.from_proforma_xml(request_body)
|
|
||||||
exercise.user = user
|
tempfile = Tempfile.new('codeharbor_import.zip')
|
||||||
saved = exercise.save
|
tempfile.write request.body.read.force_encoding('UTF-8')
|
||||||
if saved
|
tempfile.rewind
|
||||||
render :text => 'SUCCESS', :status => 200
|
|
||||||
else
|
exercise = ProformaService::Import.call(zip: tempfile, user: user_for_oauth2_request)
|
||||||
logger.info(exercise.errors.full_messages)
|
# exercise.from_proforma_xml(request_body)
|
||||||
render :text => 'Invalid exercise', :status => 400
|
# exercise.user = user
|
||||||
end
|
# saved = exercise.save
|
||||||
rescue => error
|
if exercise.save
|
||||||
if error.class == Hash
|
# render text: 'SUCCESS', status: 200
|
||||||
render :text => error.message, :status => error.status
|
render json: {status: 201}
|
||||||
else
|
else
|
||||||
raise error
|
logger.info(exercise.errors.full_messages)
|
||||||
render :text => '', :status => 500
|
render json: {status: 400}
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
# rescue => error
|
||||||
|
# if error.class == Hash
|
||||||
|
# render :text => error.message, :status => error.status
|
||||||
|
# else
|
||||||
|
# raise error
|
||||||
|
# render :text => '', :status => 500
|
||||||
|
# end
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_for_oauth2_request
|
def user_for_oauth2_request
|
||||||
@ -142,7 +149,7 @@ class ExercisesController < ApplicationController
|
|||||||
raise ({status: 401, message: 'No token in Authorization header'})
|
raise ({status: 401, message: 'No token in Authorization header'})
|
||||||
end
|
end
|
||||||
|
|
||||||
user = user_by_code_harbor_token(oauth2Token)
|
user = user_by_codeharbor_token(oauth2Token)
|
||||||
if user == nil
|
if user == nil
|
||||||
raise ({status: 401, message: 'Unknown OAuth2 token'})
|
raise ({status: 401, message: 'Unknown OAuth2 token'})
|
||||||
end
|
end
|
||||||
@ -151,13 +158,11 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
private :user_for_oauth2_request
|
private :user_for_oauth2_request
|
||||||
|
|
||||||
def user_by_code_harbor_token(oauth2Token)
|
def user_by_codeharbor_token(oauth2_token)
|
||||||
link = CodeHarborLink.where(:oauth2token => oauth2Token)[0]
|
link = CodeharborLink.where(oauth2token: oauth2_token)[0]
|
||||||
if link != nil
|
link&.user
|
||||||
return link.user
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
private :user_by_code_harbor_token
|
private :user_by_codeharbor_token
|
||||||
|
|
||||||
def exercise_params
|
def exercise_params
|
||||||
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name) if params[:exercise].present?
|
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name) if params[:exercise].present?
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
module CodeHarborLinksHelper
|
|
||||||
end
|
|
@ -1,13 +0,0 @@
|
|||||||
class CodeHarborLink < ApplicationRecord
|
|
||||||
validates :oauth2token, presence: true
|
|
||||||
validates :user_id, presence: true
|
|
||||||
|
|
||||||
belongs_to :internal_user, foreign_key: :user_id
|
|
||||||
alias_method :user, :internal_user
|
|
||||||
alias_method :user=, :internal_user=
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
oauth2token
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
12
app/models/codeharbor_link.rb
Normal file
12
app/models/codeharbor_link.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CodeharborLink < ApplicationRecord
|
||||||
|
validates :oauth2token, presence: true
|
||||||
|
validates :user_id, presence: true
|
||||||
|
|
||||||
|
belongs_to :user, foreign_key: :user_id, class_name: 'InternalUser'
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
oauth2token
|
||||||
|
end
|
||||||
|
end
|
@ -31,7 +31,7 @@ class Exercise < ApplicationRecord
|
|||||||
|
|
||||||
validate :valid_main_file?
|
validate :valid_main_file?
|
||||||
validates :description, presence: true
|
validates :description, presence: true
|
||||||
validates :execution_environment_id, presence: true
|
# validates :execution_environment_id, presence: true # TODO make this conditional - but based on what?
|
||||||
validates :public, boolean_presence: true
|
validates :public, boolean_presence: true
|
||||||
validates :title, presence: true
|
validates :title, presence: true
|
||||||
validates :token, presence: true, uniqueness: true
|
validates :token, presence: true, uniqueness: true
|
||||||
@ -49,7 +49,7 @@ class Exercise < ApplicationRecord
|
|||||||
0
|
0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def finishers_percentage
|
def finishers_percentage
|
||||||
if users.distinct.count != 0
|
if users.distinct.count != 0
|
||||||
(100.0 / users.distinct.count * finishers.count).round(2)
|
(100.0 / users.distinct.count * finishers.count).round(2)
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
class CodeHarborLinkPolicy < AdminOnlyPolicy
|
|
||||||
|
|
||||||
end
|
|
3
app/policies/codeharbor_link_policy.rb
Normal file
3
app/policies/codeharbor_link_policy.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
class CodeharborLinkPolicy < AdminOnlyPolicy
|
||||||
|
|
||||||
|
end
|
82
app/services/proforma_service/convert_task_to_exercise.rb
Normal file
82
app/services/proforma_service/convert_task_to_exercise.rb
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ProformaService
|
||||||
|
class ConvertTaskToExercise < ServiceBase
|
||||||
|
def initialize(task:, user:, exercise: nil)
|
||||||
|
@task = task
|
||||||
|
@user = user
|
||||||
|
@exercise = exercise || Exercise.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
import_exercise
|
||||||
|
@exercise
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def import_exercise
|
||||||
|
@exercise.assign_attributes(
|
||||||
|
user: @user,
|
||||||
|
title: @task.title,
|
||||||
|
description: @task.description,
|
||||||
|
instructions: @task.internal_description,
|
||||||
|
# exercise_files: task_files.values,
|
||||||
|
# execution_environment: execution_environment,
|
||||||
|
# tests: tests,
|
||||||
|
# state_list: @exercise.persisted? ? 'updated' : 'new'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def task_files
|
||||||
|
@task_files ||= Hash[
|
||||||
|
@task.all_files.reject { |file| file.id == 'ms-placeholder-file' }.map do |task_file|
|
||||||
|
[task_file.id, exercise_file_from_task_file(task_file)]
|
||||||
|
end
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def exercise_file_from_task_file(task_file)
|
||||||
|
ExerciseFile.new({
|
||||||
|
full_file_name: task_file.filename,
|
||||||
|
read_only: task_file.usage_by_lms.in?(%w[display download]),
|
||||||
|
hidden: task_file.visible == 'no',
|
||||||
|
role: task_file.internal_description
|
||||||
|
}.tap do |params|
|
||||||
|
if task_file.binary
|
||||||
|
params[:attachment] = file_base64(task_file)
|
||||||
|
params[:attachment_file_name] = task_file.filename
|
||||||
|
params[:attachment_content_type] = task_file.mimetype
|
||||||
|
else
|
||||||
|
params[:content] = task_file.content
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_base64(file)
|
||||||
|
"data:#{file.mimetype || 'image/jpeg'};base64,#{Base64.encode64(file.content)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def tests
|
||||||
|
@task.tests.map do |test_object|
|
||||||
|
Test.new(
|
||||||
|
feedback_message: test_object.meta_data['feedback-message'],
|
||||||
|
testing_framework: TestingFramework.where(
|
||||||
|
name: test_object.meta_data['testing-framework'],
|
||||||
|
version: test_object.meta_data['testing-framework-version']
|
||||||
|
).first_or_initialize,
|
||||||
|
exercise_file: test_file(test_object)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_file(test_object)
|
||||||
|
task_files.delete(test_object.files.first.id).tap { |file| file.purpose = 'test' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def execution_environment
|
||||||
|
ExecutionEnvironment.last
|
||||||
|
# ExecutionEnvironment.where(language: @task.proglang[:name], version: @task.proglang[:version]).first_or_initialize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
58
app/services/proforma_service/import.rb
Normal file
58
app/services/proforma_service/import.rb
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ProformaService
|
||||||
|
class Import < ServiceBase
|
||||||
|
def initialize(zip:, user:)
|
||||||
|
@zip = zip
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
if single_task?
|
||||||
|
importer = Proforma::Importer.new(@zip)
|
||||||
|
@task = importer.perform
|
||||||
|
exercise = ConvertTaskToExercise.call(task: @task, user: @user)
|
||||||
|
exercise.save!
|
||||||
|
|
||||||
|
exercise
|
||||||
|
else
|
||||||
|
import_multi
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def import_multi
|
||||||
|
Zip::File.open(@zip.path) do |zip_file|
|
||||||
|
zip_files = zip_file.filter { |entry| entry.name.match?(/\.zip$/) }
|
||||||
|
begin
|
||||||
|
zip_files.map! do |entry|
|
||||||
|
store_zip_entry_in_tempfile entry
|
||||||
|
end
|
||||||
|
zip_files.map do |proforma_file|
|
||||||
|
Import.call(zip: proforma_file, user: @user)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
zip_files.each(&:unlink)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_zip_entry_in_tempfile(entry)
|
||||||
|
tempfile = Tempfile.new(entry.name)
|
||||||
|
tempfile.write entry.get_input_stream.read.force_encoding('UTF-8')
|
||||||
|
tempfile.rewind
|
||||||
|
tempfile
|
||||||
|
end
|
||||||
|
|
||||||
|
def single_task?
|
||||||
|
filenames = Zip::File.open(@zip.path) do |zip_file|
|
||||||
|
zip_file.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
filenames.select { |f| f[/\.xml$/] }.any?
|
||||||
|
# rescue Zip::Error
|
||||||
|
# raise Proforma::InvalidZip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
app/services/service_base.rb
Normal file
7
app/services/service_base.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ServiceBase
|
||||||
|
def self.call(*args)
|
||||||
|
new(*args).execute
|
||||||
|
end
|
||||||
|
end
|
@ -19,5 +19,5 @@
|
|||||||
models: [ErrorTemplate, ErrorTemplateAttribute], cached: true)
|
models: [ErrorTemplate, ErrorTemplateAttribute], cached: true)
|
||||||
= render('navigation_submenu', title: t('navigation.sections.files'), models: [FileType, FileTemplate],
|
= render('navigation_submenu', title: t('navigation.sections.files'), models: [FileType, FileTemplate],
|
||||||
cached: true)
|
cached: true)
|
||||||
= render('navigation_submenu', title: t('navigation.sections.integrations'), models: [Consumer, CodeHarborLink],
|
= render('navigation_submenu', title: t('navigation.sections.integrations'), models: [Consumer],
|
||||||
cached: true)
|
cached: true)
|
||||||
|
@ -3,15 +3,15 @@ Rails.application.configure do
|
|||||||
config.webpacker.check_yarn_integrity = true
|
config.webpacker.check_yarn_integrity = true
|
||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
config.web_console.whitelisted_ips = '192.168.0.0/16'
|
config.web_console.whitelisted_ips = '192.168.0.0/16'
|
||||||
|
|
||||||
# In the development environment your application's code is reloaded on
|
# In the development environment your application's code is reloaded on
|
||||||
# every request. This slows down response time but is perfect for development
|
# every request. This slows down response time but is perfect for development
|
||||||
# since you don't have to restart the web server when you make code changes.
|
# since you don't have to restart the web server when you make code changes.
|
||||||
config.cache_classes = false
|
config.cache_classes = false
|
||||||
|
|
||||||
# Do not eager load code on boot.
|
# Do not eager load code on boot.
|
||||||
config.eager_load = false
|
config.eager_load = true
|
||||||
|
|
||||||
# Show full error reports.
|
# Show full error reports.
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = true
|
||||||
|
@ -13,7 +13,7 @@ Rails.application.routes.draw do
|
|||||||
get 'by_file_type/:file_type_id', as: :by_file_type, action: :by_file_type
|
get 'by_file_type/:file_type_id', as: :by_file_type, action: :by_file_type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :code_harbor_links
|
# resources :code_harbor_links
|
||||||
resources :request_for_comments do
|
resources :request_for_comments do
|
||||||
member do
|
member do
|
||||||
get :mark_as_solved, defaults: { format: :json }
|
get :mark_as_solved, defaults: { format: :json }
|
||||||
|
Reference in New Issue
Block a user