mumuki-laboratory 5.0.12 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +339 -0
- data/app/assets/javascripts/application/bridge.js +36 -7
- data/app/assets/javascripts/application/kids.js +92 -7
- data/app/assets/javascripts/application/submission.js +2 -1
- data/app/assets/stylesheets/application/modules/_highlight.scss +8 -0
- data/app/assets/stylesheets/application/modules/_kids.scss +92 -27
- data/app/controllers/api/base_controller.rb +23 -0
- data/app/controllers/api/courses_controller.rb +26 -0
- data/app/controllers/api/organizations_controller.rb +28 -0
- data/app/controllers/api/roles_controller.rb +47 -0
- data/app/controllers/api/students_controller.rb +9 -0
- data/app/controllers/api/teachers_controller.rb +9 -0
- data/app/controllers/api/users_controller.rb +18 -0
- data/app/controllers/assets_controller.rb +2 -1
- data/app/controllers/concerns/on_base_organization_only.rb +11 -0
- data/app/controllers/concerns/organizations_controller_template.rb +34 -0
- data/app/controllers/concerns/users_controller_template.rb +28 -0
- data/app/controllers/concerns/with_api_errors.rb +37 -0
- data/app/controllers/concerns/with_authorization.rb +19 -0
- data/app/controllers/concerns/with_errors_filter.rb +32 -0
- data/app/helpers/reset_button_helper.rb +1 -1
- data/app/models/api_client.rb +27 -0
- data/app/models/api_client_spec.rb +6 -0
- data/app/models/concerns/with_assignments.rb +4 -1
- data/app/models/course.rb +4 -6
- data/app/models/exercise/challenge.rb +5 -6
- data/app/models/language.rb +1 -1
- data/app/models/organization.rb +4 -0
- data/app/models/user.rb +22 -1
- data/app/views/exercise_solutions/_kids_results_button.html.erb +11 -0
- data/app/views/exercise_solutions/_results.html.erb +1 -7
- data/app/views/exercise_solutions/_results_button.html.erb +7 -0
- data/app/views/layouts/_kids.html.erb +5 -2
- data/app/views/layouts/exercise_inputs/editors/_custom.html.erb +1 -0
- data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +3 -0
- data/app/views/layouts/exercise_inputs/layouts/_input_kids.html.erb +3 -3
- data/app/views/layouts/modals/_kids_context.html.erb +0 -3
- data/app/views/layouts/modals/_kids_results.html.erb +2 -0
- data/config/routes.rb +23 -0
- data/db/migrate/20180129142749_add_api_client.rb +11 -0
- data/lib/mumuki/laboratory/controllers/results_rendering.rb +7 -1
- data/lib/mumuki/laboratory/extensions/string.rb +8 -0
- data/lib/mumuki/laboratory/locales/en.yml +4 -0
- data/lib/mumuki/laboratory/locales/es.yml +4 -0
- data/lib/mumuki/laboratory/locales/pt.yml +4 -0
- data/lib/mumuki/laboratory/mumukit/auth.rb +7 -0
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/public/amarillo_exito.svg +11 -0
- data/public/amarillo_fracaso.svg +5 -0
- data/public/compass_rose.svg +1417 -0
- data/spec/controllers/api_clients_controller.rb +26 -0
- data/spec/controllers/confirmations_controller_spec.rb +1 -1
- data/spec/controllers/courses_api_controller_spec.rb +28 -0
- data/spec/controllers/exercise_solutions_controller_spec.rb +1 -1
- data/spec/controllers/messages_controller_spec.rb +1 -1
- data/spec/controllers/organizations_api_controller_spec.rb +235 -0
- data/spec/controllers/students_api_controller_spec.rb +101 -0
- data/spec/controllers/users_api_controller_spec.rb +56 -0
- data/spec/dummy/db/schema.rb +9 -0
- data/spec/factories/api_client_factory.rb +18 -0
- data/spec/factories/course_factory.rb +9 -0
- data/spec/features/chapter_spec.rb +1 -1
- data/spec/features/complements_flow_spec.rb +1 -1
- data/spec/features/exams_flow_spec.rb +1 -1
- data/spec/features/exercise_flow_spec.rb +1 -1
- data/spec/features/guide_reset_spec.rb +1 -1
- data/spec/features/guides_flow_spec.rb +1 -1
- data/spec/features/home_public_flow_spec.rb +1 -1
- data/spec/features/lessons_flow_spec.rb +1 -1
- data/spec/features/links_flow_spec.rb +1 -1
- data/spec/features/login_flow_spec.rb +1 -1
- data/spec/features/profile_flow_spec.rb +1 -1
- data/spec/features/standard_flow_spec.rb +1 -1
- data/spec/helpers/application_helper_spec.rb +1 -1
- data/spec/helpers/email_helper_spec.rb +1 -1
- data/spec/helpers/exercise_input_helper_spec.rb +1 -1
- data/spec/helpers/with_breadcrumbs_spec.rb +1 -1
- data/spec/helpers/with_navigation_spec.rb +1 -1
- data/spec/models/assignment_spec.rb +1 -1
- data/spec/models/book_import_spec.rb +1 -1
- data/spec/models/book_spec.rb +1 -1
- data/spec/models/course_spec.rb +1 -1
- data/spec/models/event_generation_spec.rb +1 -1
- data/spec/models/exam_spec.rb +1 -1
- data/spec/models/exercise_spec.rb +11 -3
- data/spec/models/guide_spec.rb +1 -1
- data/spec/models/interactive_spec.rb +1 -1
- data/spec/models/lesson_spec.rb +1 -1
- data/spec/models/message_spec.rb +1 -1
- data/spec/models/navigation_spec.rb +1 -1
- data/spec/models/organization_spec.rb +1 -1
- data/spec/models/problem_spec.rb +1 -1
- data/spec/models/question_spec.rb +1 -1
- data/spec/models/reading_spec.rb +1 -1
- data/spec/models/solution_spec.rb +1 -1
- data/spec/models/usage_spec.rb +1 -1
- data/spec/models/user_changed_spec.rb +1 -1
- data/spec/models/user_spec.rb +5 -5
- data/spec/spec_helper.rb +19 -3
- metadata +37 -2
@@ -0,0 +1,28 @@
|
|
1
|
+
module UsersControllerTemplate
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :set_user!, only: [:show, :update]
|
6
|
+
before_action :set_new_user!, only: :create
|
7
|
+
before_action :protect_permissions_assignment!, only: [:create, :update]
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def protect_permissions_assignment!
|
13
|
+
current_user.protect_permissions_assignment! user_params[:permissions], @user.permissions
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_params
|
17
|
+
params.require(:user).permit(:first_name, :last_name, :email, :image_url, permissions: Mumukit::Auth::Roles::ROLES)
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_user!
|
21
|
+
@user = User.find_by uid: params[:id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_new_user!
|
25
|
+
@user = User.new user_params
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module WithApiErrors
|
2
|
+
# TODO we should extend DynamiceErrors
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
unless Rails.application.config.consider_all_requests_local
|
7
|
+
rescue_from ActionController::RoutingError, with: :not_found!
|
8
|
+
end
|
9
|
+
|
10
|
+
rescue_from ActiveRecord::RecordNotFound, with: :not_found!
|
11
|
+
rescue_from Mumukit::Auth::UnauthorizedAccessError, with: :forbidden!
|
12
|
+
rescue_from ActiveRecord::RecordInvalid, with: :bad_record!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def bad_record!(e)
|
18
|
+
render_errors! e.record.errors, 400
|
19
|
+
end
|
20
|
+
|
21
|
+
def not_found!(e)
|
22
|
+
render_error! e, 404
|
23
|
+
end
|
24
|
+
|
25
|
+
def forbidden!(e)
|
26
|
+
render_error! e, 403
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_error!(e, status)
|
30
|
+
render_errors! [e.message], status
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_errors!(errors, status)
|
34
|
+
summary = { errors: errors }
|
35
|
+
render json: summary, status: status
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WithAuthorization
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def authorize_janitor!
|
5
|
+
authorize! :janitor
|
6
|
+
end
|
7
|
+
|
8
|
+
def authorize_owner!
|
9
|
+
authorize! :owner
|
10
|
+
end
|
11
|
+
|
12
|
+
def authorization_slug
|
13
|
+
protection_slug || '_/_'
|
14
|
+
end
|
15
|
+
|
16
|
+
def protection_slug
|
17
|
+
@slug
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module WithErrorsFilter
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
around_action :catch_exceptions
|
6
|
+
rescue_from Exception do |e| catch_unhandled_errors(e) end
|
7
|
+
end
|
8
|
+
|
9
|
+
def catch_exceptions
|
10
|
+
yield
|
11
|
+
rescue ActiveRecord::RecordInvalid => e
|
12
|
+
render! e.record.errors
|
13
|
+
end
|
14
|
+
|
15
|
+
def catch_unhandled_errors(e)
|
16
|
+
summary = {
|
17
|
+
errors: {
|
18
|
+
:exception => "#{e.class.name} : #{e.message}"
|
19
|
+
}
|
20
|
+
}
|
21
|
+
summary[:trace] = e.backtrace[0, 10] if Rails.env.development?
|
22
|
+
|
23
|
+
render json: summary, status: :internal_server_error
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def render!(errors)
|
29
|
+
summary = { errors: errors }
|
30
|
+
render json: summary, status: :bad_request
|
31
|
+
end
|
32
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ResetButtonHelper
|
2
2
|
def restart_icon
|
3
|
-
fa_icon('
|
3
|
+
fa_icon('undo', title: t(:restart), class: 'fa-fw', role: 'button', tabindex: '0', 'aria-label': t(:restart))
|
4
4
|
end
|
5
5
|
|
6
6
|
def restart_guide_link(guide)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class ApiClient < ApplicationRecord
|
2
|
+
|
3
|
+
belongs_to :user
|
4
|
+
accepts_nested_attributes_for :user
|
5
|
+
validates_presence_of :description
|
6
|
+
|
7
|
+
#TODO add JWT expiration and api client invalidation
|
8
|
+
#TODO rename toke to encoded_token
|
9
|
+
before_create :set_encoded_token!
|
10
|
+
|
11
|
+
def verify!
|
12
|
+
raise 'Invalid Api Client' if Mumukit::Auth::Token.decode(token).uid != user.uid
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.verify_token!(token)
|
16
|
+
client = find_by token: token
|
17
|
+
raise 'No Api Client found for Token' unless client
|
18
|
+
client.verify!
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_encoded_token!
|
24
|
+
self.token = Mumukit::Auth::Token.encode user.uid, {}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -19,7 +19,10 @@ module WithAssignments
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def interpolate_for(user, field)
|
22
|
-
language
|
22
|
+
language
|
23
|
+
.directives_interpolations
|
24
|
+
.interpolate(field, lambda { |content| replace_content_reference(user, content) })
|
25
|
+
.first
|
23
26
|
end
|
24
27
|
|
25
28
|
def default_content_for(user)
|
data/app/models/course.rb
CHANGED
@@ -11,12 +11,10 @@ class Course < ApplicationRecord
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def slug=(slug)
|
14
|
-
|
15
|
-
self[:organization_id] = Organization.find_by(name: Mumukit::Auth::Slug.parse(slug).organization).id
|
16
|
-
end
|
14
|
+
s = Mumukit::Auth::Slug.parse(slug)
|
17
15
|
|
18
|
-
|
19
|
-
self[:
|
20
|
-
self[:
|
16
|
+
self[:slug] = slug
|
17
|
+
self[:code] = s.course
|
18
|
+
self[:organization_id] = Organization.find_by!(name: s.organization).id
|
21
19
|
end
|
22
20
|
end
|
@@ -13,12 +13,11 @@ class Challenge < Exercise
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def extra
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
16
|
+
[guide.extra, self[:extra]]
|
17
|
+
.compact
|
18
|
+
.join("\n")
|
19
|
+
.strip
|
20
|
+
.ensure_newline
|
22
21
|
end
|
23
22
|
|
24
23
|
private
|
data/app/models/language.rb
CHANGED
data/app/models/organization.rb
CHANGED
@@ -80,6 +80,10 @@ class Organization < ApplicationRecord
|
|
80
80
|
errors_explanations.try { |it| it[code.to_s] } || I18n.t(advice)
|
81
81
|
end
|
82
82
|
|
83
|
+
def self.accessible_as(user, role)
|
84
|
+
all.select { |it| it.public? || user.has_permission?(role, it.slug) }
|
85
|
+
end
|
86
|
+
|
83
87
|
private
|
84
88
|
|
85
89
|
def ensure_consistent_public_login
|
data/app/models/user.rb
CHANGED
@@ -27,6 +27,8 @@ class User < ApplicationRecord
|
|
27
27
|
|
28
28
|
after_initialize :init
|
29
29
|
|
30
|
+
before_validation :set_uid!
|
31
|
+
|
30
32
|
def last_lesson
|
31
33
|
last_guide.try(:lesson)
|
32
34
|
end
|
@@ -74,7 +76,7 @@ class User < ApplicationRecord
|
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
|
-
def
|
79
|
+
def copy_progress_to!(another)
|
78
80
|
transaction do
|
79
81
|
assignments.update_all(submitter_id: another.id)
|
80
82
|
if another.never_submitted? || last_submission_date.try { |it| it > another.last_submission_date }
|
@@ -99,8 +101,27 @@ class User < ApplicationRecord
|
|
99
101
|
Rails.application.message_verifier(:unsubscribe)
|
100
102
|
end
|
101
103
|
|
104
|
+
def attach!(role, course)
|
105
|
+
add_permission! role, course.slug
|
106
|
+
save_and_notify!
|
107
|
+
end
|
108
|
+
|
109
|
+
def detach!(role, course)
|
110
|
+
remove_permission! role, course.slug
|
111
|
+
save_and_notify!
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.create_if_necessary(user)
|
115
|
+
user[:uid] ||= user[:email]
|
116
|
+
where(uid: user[:uid]).first_or_create(user)
|
117
|
+
end
|
118
|
+
|
102
119
|
private
|
103
120
|
|
121
|
+
def set_uid!
|
122
|
+
self.uid ||= email
|
123
|
+
end
|
124
|
+
|
104
125
|
def init
|
105
126
|
self.image_url ||= "user_shape.png"
|
106
127
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div class="row">
|
2
|
+
<div class="col-md-12">
|
3
|
+
<div class="actions">
|
4
|
+
<% if assignment.passed? %>
|
5
|
+
<%= next_exercise_button(@exercise) %>
|
6
|
+
<% else %>
|
7
|
+
<button class="btn btn-success btn-block" data-dismiss="modal" aria-label="<%= t :retry_exercise %>"> <%= t :retry_exercise %> </button>
|
8
|
+
<% end %>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
</div>
|
@@ -65,10 +65,4 @@
|
|
65
65
|
|
66
66
|
<%= corollary_box @exercise unless assignment.should_retry? %>
|
67
67
|
|
68
|
-
|
69
|
-
<div class="col-md-12">
|
70
|
-
<div class="actions">
|
71
|
-
<%= next_exercise_button(@exercise) %>
|
72
|
-
</div>
|
73
|
-
</div>
|
74
|
-
</div>
|
68
|
+
<%= render partial: 'exercise_solutions/results_button', locals: {assignment: assignment} unless @exercise.input_kids? %>
|
@@ -10,6 +10,9 @@
|
|
10
10
|
<li data-target="hint" title="<%= t :need_a_hint %>"><i class="fa fa-fw fa-lightbulb-o"></i></li>
|
11
11
|
</ul>
|
12
12
|
<% end %>
|
13
|
-
<div class="
|
14
|
-
|
13
|
+
<div class="mu-kids-character-speech-bubble-normal">
|
14
|
+
<div class="description"><%= @exercise.description_task %></div>
|
15
|
+
<div class="hint" style="display: none"><%= @exercise.hint_html %></div>
|
16
|
+
</div>
|
17
|
+
<div class="mu-kids-character-speech-bubble-failed" style="display: none"></div>
|
15
18
|
</div>
|
@@ -2,4 +2,5 @@
|
|
2
2
|
|
3
3
|
<%= render_custom_editor(@exercise) %>
|
4
4
|
<%= form.hidden_field :content, id: "mu-custom-editor-value", value: @current_content %>
|
5
|
+
<%= form.hidden_field :default_content, id: "mu-custom-editor-default-value", value: @exercise.default_content %>
|
5
6
|
<%= form.hidden_field :content_extra, id: "mu-custom-editor-extra", value: @exercise.extra %>
|
@@ -7,6 +7,9 @@
|
|
7
7
|
<div class="actions mu-kids-submit-button">
|
8
8
|
<%= render_submit_button(@exercise) %>
|
9
9
|
</div>
|
10
|
+
<div class="actions mu-kids-reset-button"></div>
|
11
|
+
<img class="mu-kids-compass-rose" src="/compass_rose.svg">
|
12
|
+
<div class="mu-kids-overlay" style="display: none"></div>
|
10
13
|
<% end %>
|
11
14
|
<% else %>
|
12
15
|
<p><%= t :you_must_sign_in_before_submitting %></p>
|
@@ -20,12 +20,12 @@
|
|
20
20
|
|
21
21
|
<div class="mu-kids-states">
|
22
22
|
<div class="mu-kids-state mu-state-initial mu-kids-gbs-board-initial">
|
23
|
-
<strong
|
23
|
+
<strong id="mu-initial-state-text"><%= t :initial_state %></strong>
|
24
|
+
<strong id="mu-actual-state-text"><%= t :actual_state %></strong>
|
24
25
|
<div class="mu-kids-state-image"><%= @exercise.initial_state&.html_safe %></div>
|
25
26
|
</div>
|
26
|
-
<hr>
|
27
27
|
<div class="mu-kids-state mu-state-final">
|
28
|
-
<strong
|
28
|
+
<strong><%= t :expected_state %></strong>
|
29
29
|
<div class="mu-kids-state-image"><%= @exercise.final_state&.html_safe %></div>
|
30
30
|
</div>
|
31
31
|
</div>
|
@@ -2,9 +2,6 @@
|
|
2
2
|
<div class="modal-dialog">
|
3
3
|
<div class="modal-content">
|
4
4
|
<div class="modal-header">
|
5
|
-
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t :continue_exercise %>">
|
6
|
-
<span aria-hidden="true">×</span>
|
7
|
-
</button>
|
8
5
|
<h4 class="text-center">
|
9
6
|
<strong><%= "#{@exercise.number}. #{@exercise.name}" %></strong>
|
10
7
|
</h4>
|
data/config/routes.rb
CHANGED
@@ -49,6 +49,29 @@ Rails.application.routes.draw do
|
|
49
49
|
# Organizations assets
|
50
50
|
get '/theme_stylesheet' => 'assets#theme_stylesheet'
|
51
51
|
get '/extension_javascript' => 'assets#extension_javascript'
|
52
|
+
|
53
|
+
|
54
|
+
namespace :api do
|
55
|
+
organization_regexp = Regexp.new Mumukit::Platform::Organization::Helpers.valid_name_regex
|
56
|
+
uid_regexp = /[^\/]+/
|
57
|
+
|
58
|
+
resources :users, only: [:create, :update], constraints: {id: uid_regexp}
|
59
|
+
resources :organizations,
|
60
|
+
only: [:index, :show, :create, :update],
|
61
|
+
constraints: {id: organization_regexp}
|
62
|
+
|
63
|
+
resources :courses, only: [:create]
|
64
|
+
constraints(uid: uid_regexp) do
|
65
|
+
'/courses/:organization/:course'.tap do |it|
|
66
|
+
post "#{it}/students" => 'students#create'
|
67
|
+
post "#{it}/students/:uid/attach" => 'students#attach'
|
68
|
+
post "#{it}/students/:uid/detach" => 'students#detach'
|
69
|
+
post "#{it}/teachers" => 'teachers#create'
|
70
|
+
post "#{it}/teachers/:uid/attach" => 'teachers#attach'
|
71
|
+
post "#{it}/teachers/:uid/detach" => 'teachers#detach'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
52
75
|
end
|
53
76
|
|
54
77
|
#Rescue not found routes
|
@@ -12,7 +12,8 @@ module Mumuki::Laboratory::Controllers::ResultsRendering
|
|
12
12
|
guide_finished_by_solution: guide_finished_by_solution?,
|
13
13
|
class_for_progress_list_item: class_for_progress_list_item(@exercise, true),
|
14
14
|
html: render_results_html(assignment),
|
15
|
-
title_html: render_results_title_html(assignment)
|
15
|
+
title_html: render_results_title_html(assignment),
|
16
|
+
button_html: render_results_button_html(assignment))
|
16
17
|
end
|
17
18
|
|
18
19
|
def render_results_html(assignment)
|
@@ -25,6 +26,11 @@ module Mumuki::Laboratory::Controllers::ResultsRendering
|
|
25
26
|
locals: {assignment: assignment}
|
26
27
|
end
|
27
28
|
|
29
|
+
def render_results_button_html(assignment)
|
30
|
+
render_to_string partial: 'exercise_solutions/kids_results_button',
|
31
|
+
locals: {assignment: assignment}
|
32
|
+
end
|
33
|
+
|
28
34
|
def guide_finished_by_solution?
|
29
35
|
!@guide_previously_done && @exercise.guide_done_for?(current_user)
|
30
36
|
end
|