mumuki-domain 7.4.1 → 7.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/assignment.rb +1 -1
- data/app/models/avatar.rb +5 -0
- data/app/models/book.rb +13 -0
- data/app/models/concerns/{with_content.rb → container.rb} +1 -1
- data/app/models/concerns/contextualization.rb +53 -2
- data/app/models/concerns/disabling.rb +37 -0
- data/app/models/concerns/guide_container.rb +1 -1
- data/app/models/concerns/submittable/solvable.rb +8 -2
- data/app/models/concerns/topic_container.rb +1 -1
- data/app/models/concerns/with_discussions.rb +8 -5
- data/app/models/concerns/with_editor.rb +1 -1
- data/app/models/concerns/with_layout.rb +5 -1
- data/app/models/concerns/with_progress.rb +4 -0
- data/app/models/concerns/with_scoped_queries.rb +9 -9
- data/app/models/concerns/with_scoped_queries/page.rb +1 -1
- data/app/models/concerns/with_scoped_queries/sort.rb +6 -1
- data/app/models/course.rb +18 -7
- data/app/models/discussion.rb +47 -9
- data/app/models/exam.rb +53 -23
- data/app/models/exam/passing_criterion.rb +53 -0
- data/app/models/exercise.rb +3 -3
- data/app/models/exercise/challenge.rb +1 -1
- data/app/models/exercise/problem.rb +6 -0
- data/app/models/exercise/reading.rb +4 -0
- data/app/models/guide.rb +1 -1
- data/app/models/invitation.rb +7 -1
- data/app/models/message.rb +28 -4
- data/app/models/organization.rb +16 -0
- data/app/models/user.rb +48 -3
- data/db/migrate/20200508191543_create_avatars.rb +8 -0
- data/db/migrate/20200518135658_add_avatar_to_users.rb +5 -0
- data/db/migrate/20200527180729_add_disabled_at_to_users.rb +6 -0
- data/db/migrate/20200601203033_add_course_to_exam.rb +5 -0
- data/db/migrate/20200605161350_add_passing_criterions_to_exam.rb +6 -0
- data/db/migrate/20200608132959_add_progressive_display_lookahead_to_organizations.rb +5 -0
- data/db/migrate/20200702165503_add_messages_count_to_discussion.rb +6 -0
- data/db/migrate/20200728162727_add_not_actually_a_question_field_to_messages.rb +5 -0
- data/db/migrate/20200728163038_add_requires_moderator_response_to_discussions.rb +5 -0
- data/db/migrate/20200731081757_add_last_moderator_access_fields_to_discussion.rb +6 -0
- data/lib/mumuki/domain.rb +2 -0
- data/lib/mumuki/domain/area.rb +13 -0
- data/lib/mumuki/domain/exceptions.rb +3 -0
- data/lib/mumuki/domain/exceptions/disabled_error.rb +2 -0
- data/lib/mumuki/domain/exceptions/disabled_organization_error.rb +2 -0
- data/lib/mumuki/domain/exceptions/unprepared_organization_error.rb +2 -0
- data/lib/mumuki/domain/extensions/hash.rb +11 -2
- data/lib/mumuki/domain/extensions/string.rb +44 -0
- data/lib/mumuki/domain/factories/book_factory.rb +2 -2
- data/lib/mumuki/domain/factories/chapter_factory.rb +2 -2
- data/lib/mumuki/domain/factories/course_factory.rb +2 -0
- data/lib/mumuki/domain/factories/exam_factory.rb +2 -1
- data/lib/mumuki/domain/factories/guide_factory.rb +2 -2
- data/lib/mumuki/domain/factories/invitation_factory.rb +1 -1
- data/lib/mumuki/domain/factories/topic_factory.rb +2 -2
- data/lib/mumuki/domain/factories/user_factory.rb +1 -0
- data/lib/mumuki/domain/helpers/organization.rb +6 -1
- data/lib/mumuki/domain/helpers/user.rb +7 -7
- data/lib/mumuki/domain/locales/activerecord/en.yml +1 -0
- data/lib/mumuki/domain/locales/activerecord/es.yml +1 -0
- data/lib/mumuki/domain/locales/activerecord/pt.yml +1 -0
- data/lib/mumuki/domain/organization/settings.rb +12 -1
- data/lib/mumuki/domain/status/discussion/discussion.rb +4 -0
- data/lib/mumuki/domain/status/discussion/opened.rb +0 -8
- data/lib/mumuki/domain/submission/base.rb +6 -0
- data/lib/mumuki/domain/submission/solution.rb +3 -1
- data/lib/mumuki/domain/version.rb +1 -1
- data/lib/mumuki/domain/workspace.rb +38 -0
- metadata +24 -7
data/lib/mumuki/domain.rb
CHANGED
@@ -25,6 +25,7 @@ Mumukit::Platform.configure do |config|
|
|
25
25
|
config.organization_class_name = 'Organization'
|
26
26
|
end
|
27
27
|
|
28
|
+
require_relative './domain/area'
|
28
29
|
require_relative './domain/evaluation'
|
29
30
|
require_relative './domain/submission'
|
30
31
|
require_relative './domain/status'
|
@@ -32,6 +33,7 @@ require_relative './domain/exceptions'
|
|
32
33
|
require_relative './domain/file'
|
33
34
|
require_relative './domain/extensions'
|
34
35
|
require_relative './domain/organization'
|
36
|
+
require_relative './domain/workspace'
|
35
37
|
require_relative './domain/helpers'
|
36
38
|
require_relative './domain/syncable'
|
37
39
|
require_relative './domain/store'
|
@@ -2,4 +2,7 @@ require_relative './exceptions/forbidden_error'
|
|
2
2
|
require_relative './exceptions/gone_error'
|
3
3
|
require_relative './exceptions/not_found_error'
|
4
4
|
require_relative './exceptions/unauthorized_error'
|
5
|
+
require_relative './exceptions/disabled_error'
|
5
6
|
require_relative './exceptions/blocked_forum_error'
|
7
|
+
require_relative './exceptions/unprepared_organization_error'
|
8
|
+
require_relative './exceptions/disabled_organization_error'
|
@@ -1,5 +1,14 @@
|
|
1
1
|
class Hash
|
2
|
-
def markdownify!(*keys)
|
3
|
-
|
2
|
+
def markdownify!(*keys, **options)
|
3
|
+
warn "Don't use markdownify. Use markdownified! instead"
|
4
|
+
markdownified! *keys, **options
|
5
|
+
end
|
6
|
+
|
7
|
+
def markdownified!(*keys, **options)
|
8
|
+
keys.each { |it| self[it] = self[it].markdownified(**options) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def markdownified(*keys, **options)
|
12
|
+
map { |k, v| key.in?(keys) ? v.markdownified(options) : v }.to_h
|
4
13
|
end
|
5
14
|
end
|
@@ -27,3 +27,47 @@ class String
|
|
27
27
|
File.extname(self).delete '.'
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
|
32
|
+
# The nil-safe affable pipeline goes as follow:
|
33
|
+
#
|
34
|
+
# i18n > markdownified > sanitized > affable
|
35
|
+
#
|
36
|
+
# Where:
|
37
|
+
# * i18n: translates to current locale
|
38
|
+
# * markdownified: interpretes markdown in message and generates HTML
|
39
|
+
# * sanitized: sanitizes results HTML
|
40
|
+
# * affable: changes structure to hide low level details
|
41
|
+
#
|
42
|
+
# Other classes may polymorphically implement their own
|
43
|
+
# markdownified, sanitized and affable methods with similar semantics
|
44
|
+
# to extend this pipeline to non-strings
|
45
|
+
class String
|
46
|
+
|
47
|
+
# Creates a humman representation - but not necessary UI - representation
|
48
|
+
# of this string by interpreting its markdown as a one-liner and sanitizing it
|
49
|
+
def affable
|
50
|
+
markdownified(one_liner: true).sanitized
|
51
|
+
end
|
52
|
+
|
53
|
+
# Interprets the markdown on this string, and converts it into HTML
|
54
|
+
def markdownified(**options)
|
55
|
+
Mumukit::ContentType::Markdown.to_html self, options
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sanitizes this string, escaping unsafe HTML sequences
|
59
|
+
def sanitized
|
60
|
+
Mumukit::ContentType::Sanitizer.sanitize self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class NilClass
|
65
|
+
def affable
|
66
|
+
end
|
67
|
+
|
68
|
+
def markdownified(**options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def sanitized
|
72
|
+
end
|
73
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
factory :book do
|
3
|
-
name { Faker::Lorem.sentence(3) }
|
4
|
-
description { Faker::Lorem.sentence(30) }
|
3
|
+
name { Faker::Lorem.sentence(word_count: 3) }
|
4
|
+
description { Faker::Lorem.sentence(word_count: 30) }
|
5
5
|
slug { "mumuki/mumuki-test-book-#{SecureRandom.uuid}" }
|
6
6
|
end
|
7
7
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
|
3
3
|
factory :chapter do
|
4
|
-
number { Faker::Number.between(1, 40) }
|
4
|
+
number { Faker::Number.between(from: 1, to: 40) }
|
5
5
|
book { Organization.current.book rescue nil }
|
6
6
|
|
7
7
|
transient do
|
8
8
|
lessons { [] }
|
9
|
-
name { Faker::Lorem.sentence(3) }
|
9
|
+
name { Faker::Lorem.sentence(word_count: 3) }
|
10
10
|
slug { "mumuki/mumuki-test-topic-#{SecureRandom.uuid}" }
|
11
11
|
end
|
12
12
|
|
@@ -2,8 +2,10 @@ FactoryBot.define do
|
|
2
2
|
factory :course do
|
3
3
|
period { '2016' }
|
4
4
|
shifts { %w(morning) }
|
5
|
+
code { "k1234-#{SecureRandom.uuid}" }
|
5
6
|
days { %w(monday wednesday) }
|
6
7
|
description { 'test' }
|
7
8
|
organization_id { 1 }
|
9
|
+
slug { "#{Organization.current.name}/#{period}-#{code}" }
|
8
10
|
end
|
9
11
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
|
3
3
|
factory :exam, traits: [:guide_container] do
|
4
|
-
duration { Faker::Number.between(10, 60).minutes }
|
4
|
+
duration { Faker::Number.between(from: 10, to:60).minutes }
|
5
5
|
organization { Organization.current }
|
6
|
+
course { create(:course) }
|
6
7
|
start_time { 5.minutes.ago }
|
7
8
|
end_time { 10.minutes.since }
|
8
9
|
end
|
@@ -11,8 +11,8 @@ FactoryBot.define do
|
|
11
11
|
trait :guide_container do
|
12
12
|
transient do
|
13
13
|
exercises { [] }
|
14
|
-
name { Faker::Lorem.sentence(3) }
|
15
|
-
description { Faker::Lorem.sentence(10) }
|
14
|
+
name { Faker::Lorem.sentence(word_count: 3) }
|
15
|
+
description { Faker::Lorem.sentence(word_count: 10) }
|
16
16
|
language { create(:language) }
|
17
17
|
slug { "mumuki/mumuki-test-lesson-#{SecureRandom.uuid}" }
|
18
18
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
|
3
3
|
factory :topic do
|
4
|
-
name { Faker::Lorem::sentence(3) }
|
5
|
-
description { Faker::Lorem.paragraph(2) }
|
4
|
+
name { Faker::Lorem::sentence(word_count: 3) }
|
5
|
+
description { Faker::Lorem.paragraph(sentence_count: 2) }
|
6
6
|
slug { "mumuki/mumuki-sample-topic-#{SecureRandom.uuid}" }
|
7
7
|
locale { :en }
|
8
8
|
end
|
@@ -4,7 +4,7 @@ module Mumuki::Domain::Helpers::Organization
|
|
4
4
|
|
5
5
|
included do
|
6
6
|
delegate *Mumuki::Domain::Organization::Theme.accessors, to: :theme
|
7
|
-
delegate *Mumuki::Domain::Organization::Settings.accessors, :private?, :login_settings, to: :settings
|
7
|
+
delegate *Mumuki::Domain::Organization::Settings.accessors, :private?, :login_settings, :in_preparation?, :disabled?, to: :settings
|
8
8
|
delegate *Mumuki::Domain::Organization::Profile.accessors, :locale_json, to: :profile
|
9
9
|
end
|
10
10
|
|
@@ -48,6 +48,11 @@ module Mumuki::Domain::Helpers::Organization
|
|
48
48
|
Mumukit::Platform.application.organic_domain(name)
|
49
49
|
end
|
50
50
|
|
51
|
+
def validate_active!
|
52
|
+
raise Mumuki::Domain::DisabledOrganizationError if disabled?
|
53
|
+
raise Mumuki::Domain::UnpreparedOrganizationError if in_preparation?
|
54
|
+
end
|
55
|
+
|
51
56
|
## API Exposure
|
52
57
|
|
53
58
|
def to_param
|
@@ -28,9 +28,9 @@ module Mumuki::Domain::Helpers::User
|
|
28
28
|
role_here = "#{role}_here?"
|
29
29
|
|
30
30
|
# Tells whether this user has #{role} permissions in
|
31
|
-
# the given
|
32
|
-
define_method role_of do |
|
33
|
-
has_permission? role,
|
31
|
+
# the given `slug_like`
|
32
|
+
define_method role_of do |slug_like|
|
33
|
+
has_permission? role, slug_like.to_mumukit_slug
|
34
34
|
end
|
35
35
|
|
36
36
|
# Tells whether this user has #{role} permissions in
|
@@ -49,16 +49,16 @@ module Mumuki::Domain::Helpers::User
|
|
49
49
|
(Mumukit::Auth::Roles::ROLES - [:owner]).each do |role|
|
50
50
|
|
51
51
|
# Assignes the #{role} role to this user
|
52
|
-
# for the given
|
53
|
-
define_method "make_#{role}_of!" do |
|
54
|
-
add_permission! role,
|
52
|
+
# for the given `grant_like`
|
53
|
+
define_method "make_#{role}_of!" do |grant_like|
|
54
|
+
add_permission! role, grant_like.to_mumukit_grant
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
## Profile
|
59
59
|
|
60
60
|
def full_name
|
61
|
-
"#{first_name} #{last_name}"
|
61
|
+
"#{first_name} #{last_name}".strip
|
62
62
|
end
|
63
63
|
|
64
64
|
alias_method :name, :full_name
|
@@ -44,6 +44,7 @@ es:
|
|
44
44
|
attributes:
|
45
45
|
base:
|
46
46
|
consistent_public_login: 'Una organización pública no puede restringir los métodos de login'
|
47
|
+
invalid_activity_range: 'La fecha de deshabilitación no puede ser anterior a la de inicio del recorrido'
|
47
48
|
models:
|
48
49
|
exercise:
|
49
50
|
one: Ejercicio
|
@@ -11,7 +11,10 @@ class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
|
|
11
11
|
:embeddable?,
|
12
12
|
:immersive?,
|
13
13
|
:forum_enabled?,
|
14
|
-
:report_issue_enabled
|
14
|
+
:report_issue_enabled?,
|
15
|
+
:disabled_from,
|
16
|
+
:in_preparation_until,
|
17
|
+
:gamification_enabled?
|
15
18
|
|
16
19
|
def private?
|
17
20
|
!public?
|
@@ -24,4 +27,12 @@ class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
|
|
24
27
|
def forum_discussions_minimal_role
|
25
28
|
(@forum_discussions_minimal_role || 'student').to_sym
|
26
29
|
end
|
30
|
+
|
31
|
+
def disabled?
|
32
|
+
disabled_from.present? && disabled_from.to_datetime < DateTime.now
|
33
|
+
end
|
34
|
+
|
35
|
+
def in_preparation?
|
36
|
+
in_preparation_until.present? && in_preparation_until.to_datetime > DateTime.now
|
37
|
+
end
|
27
38
|
end
|
@@ -5,14 +5,6 @@ module Mumuki::Domain::Status::Discussion::Opened
|
|
5
5
|
true
|
6
6
|
end
|
7
7
|
|
8
|
-
def self.reachable_statuses_for_initiator(discussion)
|
9
|
-
if discussion.has_responses?
|
10
|
-
[Mumuki::Domain::Status::Discussion::PendingReview]
|
11
|
-
else
|
12
|
-
[Mumuki::Domain::Status::Discussion::Closed]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
def self.reachable_statuses_for_moderator(discussion)
|
17
9
|
if discussion.has_responses?
|
18
10
|
[Mumuki::Domain::Status::Discussion::Closed, Mumuki::Domain::Status::Discussion::Solved]
|
@@ -9,6 +9,7 @@ class Mumuki::Domain::Submission::Base
|
|
9
9
|
:submission_id, :queries, :query_results, :manual_evaluation_comment]
|
10
10
|
|
11
11
|
attr_accessor *ATTRIBUTES
|
12
|
+
attr_accessor :client_result
|
12
13
|
|
13
14
|
def self.from_attributes(*args)
|
14
15
|
new ATTRIBUTES.zip(args).to_h
|
@@ -26,6 +27,11 @@ class Mumuki::Domain::Submission::Base
|
|
26
27
|
results
|
27
28
|
end
|
28
29
|
|
30
|
+
def with_client_result(result)
|
31
|
+
self.client_result = result if result.present?
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
29
35
|
def evaluate!(assignment)
|
30
36
|
try_evaluate! assignment
|
31
37
|
rescue => e
|
@@ -2,6 +2,8 @@ class Mumuki::Domain::Submission::Solution < Mumuki::Domain::Submission::Persist
|
|
2
2
|
attr_accessor :content
|
3
3
|
|
4
4
|
def try_evaluate!(assignment)
|
5
|
-
assignment
|
5
|
+
assignment
|
6
|
+
.run_tests!({client_result: client_result}.compact.merge(content: content))
|
7
|
+
.except(:response_type)
|
6
8
|
end
|
7
9
|
end
|