api_maker 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/api_maker/api_helpers/api_maker_helpers.rb +5 -0
- data/app/api_maker/services/can_can/load_abilities.rb +30 -0
- data/app/api_maker/services/devise/sign_in.rb +64 -0
- data/app/api_maker/services/devise/sign_out.rb +9 -0
- data/app/api_maker/services/models/find_or_create_by.rb +18 -0
- data/app/channels/api_maker/subscriptions_channel.rb +33 -2
- data/app/controllers/api_maker/base_controller.rb +7 -3
- data/app/controllers/api_maker/commands_controller.rb +26 -4
- data/app/controllers/api_maker/session_statuses_controller.rb +1 -1
- data/app/services/api_maker/abilities_loader.rb +104 -0
- data/app/services/api_maker/application_service.rb +2 -1
- data/app/services/api_maker/base_command.rb +248 -0
- data/app/services/api_maker/collection_command_service.rb +29 -15
- data/app/services/api_maker/collection_loader.rb +124 -0
- data/app/services/api_maker/command_failed_error.rb +3 -0
- data/app/services/api_maker/command_response.rb +17 -6
- data/app/services/api_maker/command_service.rb +3 -3
- data/app/services/api_maker/create_command.rb +11 -26
- data/app/services/api_maker/create_command_service.rb +3 -3
- data/app/services/api_maker/database_type.rb +9 -0
- data/app/services/api_maker/deep_merge_params.rb +26 -0
- data/app/services/api_maker/deserializer.rb +35 -0
- data/app/services/api_maker/destroy_command.rb +15 -21
- data/app/services/api_maker/destroy_command_service.rb +3 -3
- data/app/services/api_maker/generate_react_native_api_service.rb +3 -19
- data/app/services/api_maker/include_helpers.rb +17 -0
- data/app/services/api_maker/index_command.rb +8 -88
- data/app/services/api_maker/index_command_service.rb +5 -5
- data/app/services/api_maker/js_method_namer_service.rb +1 -1
- data/app/services/api_maker/locals_from_controller.rb +14 -0
- data/app/services/api_maker/member_command_service.rb +15 -13
- data/app/services/api_maker/model_classes_java_script_generator_service.rb +37 -0
- data/app/services/api_maker/model_content_generator_service.rb +17 -21
- data/app/services/api_maker/models/save.rb +29 -0
- data/app/services/api_maker/models_finder_service.rb +6 -2
- data/app/services/api_maker/models_generator_service.rb +6 -43
- data/app/services/api_maker/move_components_to_routes.rb +50 -0
- data/app/services/api_maker/primary_id_for_model.rb +6 -0
- data/app/services/api_maker/reset_indexed_db_service.rb +36 -0
- data/app/services/api_maker/routes_file_reloader.rb +20 -0
- data/app/services/api_maker/select_columns_on_collection.rb +78 -0
- data/app/services/api_maker/select_parser.rb +32 -0
- data/app/services/api_maker/service_command.rb +27 -0
- data/app/services/api_maker/service_command_service.rb +14 -0
- data/app/services/api_maker/simple_model_errors.rb +52 -0
- data/app/services/api_maker/update_command.rb +8 -24
- data/app/services/api_maker/update_command_service.rb +3 -3
- data/app/services/api_maker/valid_command.rb +4 -13
- data/app/services/api_maker/valid_command_service.rb +3 -3
- data/app/services/api_maker/validation_errors_generator_service.rb +146 -0
- data/app/views/api_maker/_data.html.erb +17 -11
- data/config/routes.rb +0 -2
- data/lib/api_maker/ability.rb +22 -7
- data/lib/api_maker/ability_loader.rb +9 -6
- data/lib/api_maker/base_collection_instance.rb +15 -0
- data/lib/api_maker/base_resource.rb +135 -9
- data/lib/api_maker/base_service.rb +14 -0
- data/lib/api_maker/collection_serializer.rb +95 -34
- data/lib/api_maker/command_spec_helper.rb +41 -11
- data/lib/api_maker/configuration.rb +31 -4
- data/lib/api_maker/expect_to_able_to_helper.rb +31 -0
- data/lib/api_maker/individual_command.rb +24 -9
- data/lib/api_maker/javascript/model-template.js.erb +39 -25
- data/lib/api_maker/javascript/models.js.erb +6 -0
- data/lib/api_maker/loader.rb +1 -1
- data/lib/api_maker/memory_storage.rb +1 -1
- data/lib/api_maker/model_extensions.rb +34 -18
- data/lib/api_maker/permitted_params_argument.rb +5 -1
- data/lib/api_maker/preloader.rb +71 -32
- data/lib/api_maker/preloader_base.rb +108 -0
- data/lib/api_maker/preloader_belongs_to.rb +34 -33
- data/lib/api_maker/preloader_has_many.rb +45 -39
- data/lib/api_maker/preloader_has_one.rb +30 -47
- data/lib/api_maker/railtie.rb +3 -11
- data/lib/api_maker/relationship_preloader.rb +42 -0
- data/lib/api_maker/resource_routing.rb +18 -4
- data/lib/api_maker/result_parser.rb +34 -20
- data/lib/api_maker/serializer.rb +53 -22
- data/lib/api_maker/spec_helper/browser_logs.rb +14 -0
- data/lib/api_maker/spec_helper/execute_collection_command.rb +46 -0
- data/lib/api_maker/spec_helper/execute_member_command.rb +52 -0
- data/lib/api_maker/spec_helper/expect_no_browser_errors.rb +18 -0
- data/lib/api_maker/spec_helper/wait_for_expect.rb +20 -0
- data/lib/api_maker/spec_helper/wait_for_flash_message.rb +21 -0
- data/lib/api_maker/spec_helper.rb +112 -48
- data/lib/api_maker/version.rb +1 -1
- data/lib/api_maker.rb +7 -3
- metadata +108 -89
- data/README.md +0 -476
- data/app/controllers/api_maker/devise_controller.rb +0 -60
- data/lib/api_maker/base_command.rb +0 -81
- data/lib/api_maker/javascript/api.js +0 -92
- data/lib/api_maker/javascript/base-model.js +0 -543
- data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +0 -16
- data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +0 -47
- data/lib/api_maker/javascript/bootstrap/card.jsx +0 -79
- data/lib/api_maker/javascript/bootstrap/checkbox.jsx +0 -127
- data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +0 -105
- data/lib/api_maker/javascript/bootstrap/live-table.jsx +0 -168
- data/lib/api_maker/javascript/bootstrap/money-input.jsx +0 -136
- data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +0 -80
- data/lib/api_maker/javascript/bootstrap/select.jsx +0 -168
- data/lib/api_maker/javascript/bootstrap/string-input.jsx +0 -203
- data/lib/api_maker/javascript/cable-connection-pool.js +0 -169
- data/lib/api_maker/javascript/cable-subscription-pool.js +0 -111
- data/lib/api_maker/javascript/cable-subscription.js +0 -33
- data/lib/api_maker/javascript/collection.js +0 -186
- data/lib/api_maker/javascript/commands-pool.js +0 -123
- data/lib/api_maker/javascript/custom-error.js +0 -14
- data/lib/api_maker/javascript/deserializer.js +0 -35
- data/lib/api_maker/javascript/devise.js.erb +0 -113
- data/lib/api_maker/javascript/error-logger.js +0 -119
- data/lib/api_maker/javascript/event-connection.jsx +0 -24
- data/lib/api_maker/javascript/event-created.jsx +0 -26
- data/lib/api_maker/javascript/event-destroyed.jsx +0 -26
- data/lib/api_maker/javascript/event-emitter-listener.jsx +0 -32
- data/lib/api_maker/javascript/event-listener.jsx +0 -41
- data/lib/api_maker/javascript/event-updated.jsx +0 -26
- data/lib/api_maker/javascript/form-data-to-object.js +0 -70
- data/lib/api_maker/javascript/included.js +0 -39
- data/lib/api_maker/javascript/key-value-store.js +0 -47
- data/lib/api_maker/javascript/logger.js +0 -23
- data/lib/api_maker/javascript/model-name.js +0 -21
- data/lib/api_maker/javascript/models-response-reader.js +0 -43
- data/lib/api_maker/javascript/paginate.jsx +0 -128
- data/lib/api_maker/javascript/params.js +0 -68
- data/lib/api_maker/javascript/resource-route.jsx +0 -75
- data/lib/api_maker/javascript/resource-routes.jsx +0 -36
- data/lib/api_maker/javascript/result.js +0 -25
- data/lib/api_maker/javascript/session-status-updater.js +0 -113
- data/lib/api_maker/javascript/sort-link.jsx +0 -88
- data/lib/api_maker/javascript/updated-attribute.jsx +0 -60
- data/lib/api_maker/preloader_through.rb +0 -101
- data/lib/api_maker/relationship_includer.rb +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 696d6f1f8b6baa82e326b7d05f4e6f6fdd57de5a7ad9a20d7961d1d7ec361e78
|
|
4
|
+
data.tar.gz: 8960e2876a0c29d700c9c429e0153f43ea31e0839a03d423ffeeb92863f18ee2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c185f283cb899f81293df2e93e91c9298a4c1bfe5abbc5eb56f4fd23444ff46ce42776718cc2547e103ff511e30406fdb62761363813c68d9e67375cbcee2363
|
|
7
|
+
data.tar.gz: 40be89f333195778c4c7455d4f76ec85a1786637346e75f91dd043296870d79f340b53193bf28420962c6f7f418858697e4daa0e803684d29af20ebb6481076a
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Services::CanCan::LoadAbilities < ApiMaker::BaseService
|
|
2
|
+
def perform
|
|
3
|
+
result = []
|
|
4
|
+
|
|
5
|
+
request.each do |ability_data|
|
|
6
|
+
# Sometimes Rails passes a hash instead of an array
|
|
7
|
+
ability_data = ability_data.fetch(1) if ability_data.is_a?(Array)
|
|
8
|
+
|
|
9
|
+
ability = ability_data.fetch("ability")
|
|
10
|
+
subject = ability_data.fetch("subject")
|
|
11
|
+
subject_to_check = subject
|
|
12
|
+
|
|
13
|
+
# Convert subject to original model class if resource is given
|
|
14
|
+
subject_to_check = subject.model_class if subject.is_a?(Class) && subject < ApiMaker::BaseResource
|
|
15
|
+
|
|
16
|
+
can = current_ability.can?(ability.to_sym, subject_to_check)
|
|
17
|
+
result << {
|
|
18
|
+
ability: ability,
|
|
19
|
+
can: can,
|
|
20
|
+
subject: subject
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
succeed!(abilities: result)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def request
|
|
28
|
+
@request ||= args.fetch(:request)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class Services::Devise::SignIn < ApiMaker::BaseService
|
|
2
|
+
include Devise::Controllers::Rememberable
|
|
3
|
+
|
|
4
|
+
# Rememberable needs this
|
|
5
|
+
def cookies
|
|
6
|
+
controller.__send__(:cookies)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def perform
|
|
10
|
+
fail! "Devise sign in isn't enabled", type: :devise_sign_in_isnt_enabled unless ApiMaker::Configuration.current.devise_sign_in_enabled
|
|
11
|
+
|
|
12
|
+
check_model_exists
|
|
13
|
+
check_serializer_exists
|
|
14
|
+
|
|
15
|
+
if !model.active_for_authentication?
|
|
16
|
+
fail! inactive_message, type: :inactive
|
|
17
|
+
elsif model.valid_password?(args[:password])
|
|
18
|
+
controller.sign_in(model, scope: scope)
|
|
19
|
+
remember_me(model) if args.dig(:args, :rememberMe)
|
|
20
|
+
succeed!(model_data: serializer.result)
|
|
21
|
+
else
|
|
22
|
+
fail! invalid_error_message, type: :invalid
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_model_exists
|
|
27
|
+
error_msg = I18n.t("devise.failure.not_found_in_database", authentication_keys: model_class.authentication_keys.join(", "))
|
|
28
|
+
fail! error_msg, type: :not_found_in_database unless model
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def check_serializer_exists
|
|
32
|
+
fail! "Serializer doesn't exist for #{scope}", type: :serializer_doesnt_exist unless resource
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inactive_message
|
|
36
|
+
message = model.inactive_message
|
|
37
|
+
message = I18n.t("devise.failure.#{message}") if message.is_a?(Symbol)
|
|
38
|
+
message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def invalid_error_message
|
|
42
|
+
I18n.t("devise.failure.invalid", authentication_keys: model_class.authentication_keys.join(", "))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def model
|
|
46
|
+
@model ||= model_class.find_for_authentication(email: args[:username])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def model_class
|
|
50
|
+
@model_class ||= scope.camelize.safe_constantize
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def scope
|
|
54
|
+
@scope ||= args.dig(:args, :scope).presence || "user"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resource
|
|
58
|
+
@resource ||= ApiMaker::Serializer.resource_for(model.class)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def serializer
|
|
62
|
+
@serializer ||= ApiMaker::Serializer.new(ability: current_ability, api_maker_args: api_maker_args, model: model)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class Services::Devise::SignOut < ApiMaker::BaseService
|
|
2
|
+
def perform
|
|
3
|
+
fail! "Devise sign out isn't enabled", type: :devise_sign_out_isnt_enabled unless ApiMaker::Configuration.current.devise_sign_out_enabled
|
|
4
|
+
scope = args.dig(:args, :scope).presence || "user"
|
|
5
|
+
current_model = controller.__send__("current_#{scope}")
|
|
6
|
+
controller.sign_out current_model
|
|
7
|
+
succeed!(success: true)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Services::Models::FindOrCreateBy < ApiMaker::BaseService
|
|
2
|
+
def perform
|
|
3
|
+
resource_name = args.fetch(:resource_name)
|
|
4
|
+
resource = "Resources::#{resource_name}Resource".safe_constantize
|
|
5
|
+
model_class = resource.model_class
|
|
6
|
+
find_or_create_by_args = args.fetch(:find_or_create_by_args)
|
|
7
|
+
additional_data = args[:additional_data]
|
|
8
|
+
|
|
9
|
+
model = model_class.find_or_initialize_by(find_or_create_by_args)
|
|
10
|
+
model.assign_attributes(additional_data) if model.new_record? && additional_data
|
|
11
|
+
|
|
12
|
+
if model.save
|
|
13
|
+
succeed! model: model
|
|
14
|
+
else
|
|
15
|
+
fail! errors: model.errors.full_messages
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
class ApiMaker::SubscriptionsChannel < ApplicationCable::Channel
|
|
2
2
|
def subscribed
|
|
3
3
|
params[:subscription_data].each do |model_name, subscription_types|
|
|
4
|
+
subscription_types["model_class_events"]&.each do |event_name|
|
|
5
|
+
connect_model_class_event(model_name, event_name)
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
subscription_types["events"]&.each do |event_name, model_ids|
|
|
5
9
|
connect_event(model_name, model_ids, event_name)
|
|
6
10
|
end
|
|
@@ -25,16 +29,24 @@ private
|
|
|
25
29
|
model_class = model_for_resource_name(model_name)
|
|
26
30
|
channel_name = model_class.api_maker_broadcast_create_channel_name
|
|
27
31
|
stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
|
|
32
|
+
ApiMaker::Configuration.current.before_create_event_callbacks.each do |callback|
|
|
33
|
+
callback.call(data: data)
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
# We need to look the model up to evaluate if the user has access
|
|
29
|
-
|
|
37
|
+
model_class = data.fetch("mcn").safe_constantize
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
Rails.logger.debug { "API maker: ConnectCreates for #{model_class.name}" }
|
|
40
|
+
model = model_class.accessible_by(current_ability, :create_events).find_by(model_class.primary_key => data.fetch("mi"))
|
|
41
|
+
|
|
42
|
+
# Transmit the data to JS if its found (and thereby allowed)
|
|
32
43
|
transmit data if model
|
|
33
44
|
end
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
def connect_destroys(model_name, model_ids)
|
|
37
48
|
model_class = model_for_resource_name(model_name)
|
|
49
|
+
Rails.logger.debug { "API maker: ConnectDestroys for #{model_class.name}" }
|
|
38
50
|
models = model_class.accessible_by(current_ability, :destroy_events).where(model_class.primary_key => model_ids)
|
|
39
51
|
models.each do |model|
|
|
40
52
|
channel_name = model.api_maker_broadcast_destroy_channel_name
|
|
@@ -47,6 +59,7 @@ private
|
|
|
47
59
|
def connect_event(model_name, model_ids, event_name)
|
|
48
60
|
ability_name = "event_#{event_name}".to_sym
|
|
49
61
|
model_class = model_for_resource_name(model_name)
|
|
62
|
+
Rails.logger.debug { "API maker: ConnectEvents for #{model_class.name} #{event_name}" }
|
|
50
63
|
models = model_class.accessible_by(current_ability, ability_name).where(model_class.primary_key => model_ids)
|
|
51
64
|
models.each do |model|
|
|
52
65
|
channel_name = model.api_maker_event_channel_name(event_name)
|
|
@@ -56,8 +69,26 @@ private
|
|
|
56
69
|
end
|
|
57
70
|
end
|
|
58
71
|
|
|
72
|
+
def connect_model_class_event(model_name, event_name)
|
|
73
|
+
ability_name = "model_class_event_#{event_name}".to_sym
|
|
74
|
+
model_class = model_for_resource_name(model_name)
|
|
75
|
+
channel_name = model_class.api_maker_model_class_event_name(event_name)
|
|
76
|
+
|
|
77
|
+
Rails.logger.debug { "API maker: ConnectModelClassEvent for #{model_class.name} #{event_name}" }
|
|
78
|
+
|
|
79
|
+
if current_ability.can?(ability_name, model_class)
|
|
80
|
+
stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
|
|
81
|
+
transmit data
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
Rails.logger.warn { "API maker: No access to model class event #{model_class.name}##{event_name} with ability name: #{ability_name}" }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
59
88
|
def connect_updates(model_name, model_ids)
|
|
60
89
|
model_class = model_for_resource_name(model_name)
|
|
90
|
+
Rails.logger.debug { "API maker: ConnectUpdates for #{model_class.name}" }
|
|
91
|
+
|
|
61
92
|
models = model_class.accessible_by(current_ability, :update_events).where(model_class.primary_key => model_ids)
|
|
62
93
|
models.each do |model|
|
|
63
94
|
channel_name = model.api_maker_broadcast_update_channel_name
|
|
@@ -8,15 +8,19 @@ class ApiMaker::BaseController < ApplicationController
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def current_ability
|
|
11
|
-
@current_ability ||=
|
|
11
|
+
@current_ability ||= ApiMaker::Configuration.current.ability_class.new(api_maker_args: api_maker_args, locals: api_maker_locals)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def api_maker_locals
|
|
15
|
+
@api_maker_locals ||= {}
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
def render_error(error)
|
|
15
19
|
puts error.inspect
|
|
16
|
-
puts error.backtrace
|
|
20
|
+
puts Rails.backtrace_cleaner.clean(error.backtrace)
|
|
17
21
|
|
|
18
22
|
logger.error error.inspect
|
|
19
|
-
logger.error error.backtrace.join("\n")
|
|
23
|
+
logger.error Rails.backtrace_cleaner.clean(error.backtrace).join("\n")
|
|
20
24
|
|
|
21
25
|
raise error
|
|
22
26
|
end
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
class ApiMaker::CommandsController < ApiMaker::BaseController
|
|
2
|
+
wrap_parameters false
|
|
3
|
+
|
|
2
4
|
def create
|
|
3
5
|
command_response = ApiMaker::CommandResponse.new(controller: self)
|
|
4
6
|
controller = self
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
merged_params.fetch(:pool).each do |command_type, command_type_data|
|
|
7
9
|
command_type_data.each do |resource_plural_name, command_model_data|
|
|
8
10
|
command_model_data.each do |command_name, command_data|
|
|
9
11
|
ApiMaker.const_get("#{command_type.camelize}CommandService").execute!(
|
|
10
12
|
ability: current_ability,
|
|
11
|
-
|
|
13
|
+
api_maker_args: api_maker_args,
|
|
12
14
|
command_response: command_response,
|
|
13
15
|
commands: command_data,
|
|
14
16
|
command_name: command_name,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
controller: controller,
|
|
18
|
+
resource_name: resource_plural_name
|
|
17
19
|
)
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -23,4 +25,24 @@ class ApiMaker::CommandsController < ApiMaker::BaseController
|
|
|
23
25
|
|
|
24
26
|
render json: {responses: command_response.result}
|
|
25
27
|
end
|
|
28
|
+
|
|
29
|
+
def json_params
|
|
30
|
+
@json_params ||= ActionController::Parameters.new(json_data) if json_data
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def json_data
|
|
34
|
+
@json_data ||= JSON.parse(params[:json]) if params[:json]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def merged_params
|
|
38
|
+
@merged_params ||= if json_data
|
|
39
|
+
raw_data = params.permit!.to_h
|
|
40
|
+
merged_data = ApiMaker::DeepMergeParams.execute!(json_data, raw_data)
|
|
41
|
+
ActionController::Parameters.new(merged_data)
|
|
42
|
+
elsif json_params
|
|
43
|
+
json_params
|
|
44
|
+
else
|
|
45
|
+
params
|
|
46
|
+
end
|
|
47
|
+
end
|
|
26
48
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
class ApiMaker::AbilitiesLoader < ApiMaker::ApplicationService
|
|
2
|
+
attr_reader :abilities, :abilities_data, :abilities_with_no_conditions, :abilities_with_no_rules, :ability, :groupings, :serializers
|
|
3
|
+
|
|
4
|
+
def initialize(abilities:, ability:, serializers:)
|
|
5
|
+
@ability = ability
|
|
6
|
+
@abilities = abilities.map(&:to_sym)
|
|
7
|
+
@abilities_data = {}
|
|
8
|
+
@abilities_with_no_conditions = []
|
|
9
|
+
@abilities_with_no_rules = []
|
|
10
|
+
@groupings = {}
|
|
11
|
+
@serializers = serializers
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def perform
|
|
15
|
+
scan_abilities
|
|
16
|
+
scan_groupings
|
|
17
|
+
|
|
18
|
+
load_abilities_with_no_conditions if abilities_with_no_conditions.any?
|
|
19
|
+
load_abilities_with_no_rules if abilities_with_no_rules.any?
|
|
20
|
+
|
|
21
|
+
groupings.each_value do |ability_names|
|
|
22
|
+
load_abilities_with_conditions(ability_names)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
succeed!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def scan_abilities
|
|
29
|
+
abilities.each do |ability_name|
|
|
30
|
+
abilities_data[ability_name] = {
|
|
31
|
+
combined_conditions: [],
|
|
32
|
+
rules_count: 0,
|
|
33
|
+
rule_with_no_conditions: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
relevant_rules = ability.__send__(:relevant_rules, ability_name, model_class)
|
|
37
|
+
relevant_rules.each do |can_can_rule|
|
|
38
|
+
abilities_data[ability_name][:combined_conditions] << can_can_rule.conditions.to_s
|
|
39
|
+
abilities_data[ability_name][:rules_count] += 1
|
|
40
|
+
|
|
41
|
+
if can_can_rule.__send__(:conditions_empty?)
|
|
42
|
+
abilities_data[ability_name][:rule_with_no_conditions] = true
|
|
43
|
+
abilities_with_no_conditions << ability_name
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def scan_groupings
|
|
50
|
+
abilities_data.each do |ability_name, ability_data|
|
|
51
|
+
# No reason to create a group with no conditions - we are giving access to these without doing a query elsewhere
|
|
52
|
+
next if ability_data.fetch(:rule_with_no_conditions)
|
|
53
|
+
|
|
54
|
+
# If no rules have been defined, then we can safely assume no access without doing a query elsewhere
|
|
55
|
+
if ability_data.fetch(:rules_count).zero?
|
|
56
|
+
abilities_with_no_rules << ability_name
|
|
57
|
+
else
|
|
58
|
+
identifier = ability_data[:combined_conditions].join("___")
|
|
59
|
+
|
|
60
|
+
groupings[identifier] ||= []
|
|
61
|
+
groupings[identifier] << ability_name
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
groupings
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def load_abilities_with_no_conditions
|
|
69
|
+
Rails.logger.debug { "API maker: Loading abilities with no condition: [#{abilities_with_no_conditions.join(", ")}], #{model_class}" }
|
|
70
|
+
abilities_with_no_conditions.each do |ability_name|
|
|
71
|
+
serializers.each do |serializer|
|
|
72
|
+
serializer.load_ability(ability_name, true)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def load_abilities_with_no_rules
|
|
78
|
+
Rails.logger.debug { "API maker: Loading abilities with no rules: [#{abilities_with_no_rules.join(", ")}], #{model_class}" }
|
|
79
|
+
abilities_with_no_rules.each do |ability_name|
|
|
80
|
+
serializers.each do |serializer|
|
|
81
|
+
serializer.load_ability(ability_name, false)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def load_abilities_with_conditions(ability_names)
|
|
87
|
+
Rails.logger.debug { "API maker: Loading abilities with conditions through query: [#{ability_names.join(", ")}], #{model_class}" }
|
|
88
|
+
|
|
89
|
+
model_ids = model_class
|
|
90
|
+
.accessible_by(ability, ability_names.first)
|
|
91
|
+
.where(id: serializers.map(&:id))
|
|
92
|
+
.pluck(model_class.primary_key)
|
|
93
|
+
|
|
94
|
+
serializers.each do |serializer|
|
|
95
|
+
ability_names.each do |ability_name|
|
|
96
|
+
serializer.load_ability(ability_name, model_ids.include?(serializer.id))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def model_class
|
|
102
|
+
@model_class ||= serializers.first.model.class
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
class ApiMaker::ApplicationService < ServicePattern::Service
|
|
2
|
+
# Replaces magic variables with actual variable names
|
|
2
3
|
def api_maker_json(object)
|
|
3
4
|
json = object.to_json
|
|
4
|
-
json.gsub!(/"\{\{(
|
|
5
|
+
json.gsub!(/"\{\{([A-z_]+?)\}\}"/, "\\1")
|
|
5
6
|
json
|
|
6
7
|
end
|
|
7
8
|
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
class ApiMaker::BaseCommand
|
|
2
|
+
ApiMaker::IncludeHelpers.execute!(klass: self)
|
|
3
|
+
|
|
4
|
+
attr_reader :api_maker_args, :collection, :collection_instance, :command, :commands, :command_response, :controller, :current_ability
|
|
5
|
+
|
|
6
|
+
delegate :args, :model, :model_id, to: :command
|
|
7
|
+
delegate :result_for_command, to: :command_response
|
|
8
|
+
|
|
9
|
+
# Returns true if the gem "goldiloader" is present in the app
|
|
10
|
+
def self.goldiloader?
|
|
11
|
+
@goldiloader = Gem::Specification.find_all_by_name("goldiloader").any? if @goldiloader.nil?
|
|
12
|
+
@goldiloader
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(ability:, api_maker_args:, collection:, collection_instance:, command:, commands:, command_response:, controller:)
|
|
16
|
+
@api_maker_args = api_maker_args
|
|
17
|
+
@current_ability = ability
|
|
18
|
+
@collection = collection
|
|
19
|
+
@collection_instance = collection_instance
|
|
20
|
+
@command = command
|
|
21
|
+
@commands = commands
|
|
22
|
+
@command_response = command_response
|
|
23
|
+
@controller = controller
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute_with_response
|
|
27
|
+
execute!
|
|
28
|
+
rescue ApiMaker::CommandFailedError => e
|
|
29
|
+
command.fail(*e.api_maker_args, &e.api_maker_block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.command_error_message(error)
|
|
33
|
+
if Rails.application.config.consider_all_requests_local
|
|
34
|
+
"#{error.class.name}: #{error.message}"
|
|
35
|
+
else
|
|
36
|
+
"Internal server error"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.execute_in_thread!(ability:, api_maker_args:, collection:, commands:, command_response:, controller:)
|
|
41
|
+
command_response.with_thread do
|
|
42
|
+
if const_defined?(:CollectionInstance)
|
|
43
|
+
collection_instance = const_get(:CollectionInstance).new(
|
|
44
|
+
ability: ability,
|
|
45
|
+
api_maker_args: api_maker_args,
|
|
46
|
+
collection: collection,
|
|
47
|
+
commands: commands,
|
|
48
|
+
command_response: command_response,
|
|
49
|
+
controller: controller
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
collection = collection_instance.custom_collection if collection_instance.respond_to?(:custom_collection)
|
|
53
|
+
collection_instance.collection = collection
|
|
54
|
+
|
|
55
|
+
threadded = collection_instance.try(:threadded?)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if threadded
|
|
59
|
+
# Goldiloader doesn't work with threads (loads all relationships for each thread)
|
|
60
|
+
collection = collection.auto_include(false) if ApiMaker::BaseCommand.goldiloader?
|
|
61
|
+
|
|
62
|
+
# Load relationship before commands so each command doesn't query on its own
|
|
63
|
+
collection.load
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
each_command(collection: collection, command_response: command_response, commands: commands, controller: controller, threadded: threadded) do |command|
|
|
67
|
+
command_instance = new(
|
|
68
|
+
ability: ability,
|
|
69
|
+
api_maker_args: api_maker_args,
|
|
70
|
+
collection: collection,
|
|
71
|
+
collection_instance: collection_instance,
|
|
72
|
+
command: command,
|
|
73
|
+
commands: command,
|
|
74
|
+
command_response: command_response,
|
|
75
|
+
controller: controller
|
|
76
|
+
)
|
|
77
|
+
command_instance.execute_with_response
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.each_command(collection:, command_response:, commands:, controller:, threadded:, &blk)
|
|
83
|
+
commands.each do |command_id, command_data|
|
|
84
|
+
if threadded
|
|
85
|
+
command_response.with_thread do
|
|
86
|
+
run_command(
|
|
87
|
+
command_id: command_id,
|
|
88
|
+
command_data: command_data,
|
|
89
|
+
command_response: command_response,
|
|
90
|
+
collection: collection,
|
|
91
|
+
controller: controller,
|
|
92
|
+
&blk
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
run_command(
|
|
97
|
+
command_id: command_id,
|
|
98
|
+
command_data: command_data,
|
|
99
|
+
command_response: command_response,
|
|
100
|
+
collection: collection,
|
|
101
|
+
controller: controller,
|
|
102
|
+
&blk
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.run_command(collection:, command_id:, command_data:, command_response:, controller:)
|
|
109
|
+
command = ApiMaker::IndividualCommand.new(
|
|
110
|
+
args: ApiMaker::Deserializer.execute!(arg: command_data[:args]),
|
|
111
|
+
collection: collection,
|
|
112
|
+
command: self,
|
|
113
|
+
id: command_id,
|
|
114
|
+
primary_key: command_data[:primary_key],
|
|
115
|
+
response: command_response
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
yield command
|
|
120
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
121
|
+
error_response = {
|
|
122
|
+
success: false,
|
|
123
|
+
errors: [{message: command_error_message(e), type: :runtime_error}]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Rails.logger.error e.message
|
|
127
|
+
Rails.logger.error Rails.backtrace_cleaner.clean(e.backtrace).join("\n")
|
|
128
|
+
|
|
129
|
+
ApiMaker::Configuration.current.report_error(
|
|
130
|
+
command: command,
|
|
131
|
+
controller: controller,
|
|
132
|
+
error: e,
|
|
133
|
+
response: error_response
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
command.error(error_response)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def execute_service_or_fail(service_class, *args, &blk)
|
|
141
|
+
response = service_class.execute(*args, &blk)
|
|
142
|
+
|
|
143
|
+
if response.success?
|
|
144
|
+
succeed!(success: true)
|
|
145
|
+
else
|
|
146
|
+
fail_command_from_service_error_response(response)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def fail_command_from_service_error_response(response)
|
|
151
|
+
fail!(errors: serialize_service_errors(response.errors))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def failure_response(errors:)
|
|
155
|
+
fail!(
|
|
156
|
+
model: serialized_model(model),
|
|
157
|
+
success: false,
|
|
158
|
+
errors: errors
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def failure_save_response(additional_attributes: [], model:, params:, simple_model_errors: false)
|
|
163
|
+
raise "Cannot receive additional attributes unless simple model errors" if !additional_attributes.empty? && !simple_model_errors
|
|
164
|
+
|
|
165
|
+
error_messages = if simple_model_errors
|
|
166
|
+
ApiMaker::SimpleModelErrors.execute!(additional_attributes: additional_attributes, model: model)
|
|
167
|
+
else
|
|
168
|
+
model.errors.full_messages
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
fail!(
|
|
172
|
+
error_type: :validation_error,
|
|
173
|
+
errors: error_messages.map { |error_message| {message: error_message, type: :validation_error} },
|
|
174
|
+
model: serialized_model(model),
|
|
175
|
+
success: false,
|
|
176
|
+
validation_errors: ApiMaker::ValidationErrorsGeneratorService.execute!(model: model, params: params)
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def model_class
|
|
181
|
+
@model_class ||= collection.klass
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def save_models_or_fail(*models, simple_model_errors: false)
|
|
185
|
+
response = ApiMaker::Models::Save.execute(models: models, simple_model_errors: simple_model_errors)
|
|
186
|
+
|
|
187
|
+
if response.success?
|
|
188
|
+
succeed!(success: true)
|
|
189
|
+
true
|
|
190
|
+
else
|
|
191
|
+
fail!(errors: response.error_messages.map { |error| {message: error, type: :validation_error} })
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def serialize_service_errors(errors)
|
|
196
|
+
errors.map do |error|
|
|
197
|
+
{
|
|
198
|
+
message: error.message,
|
|
199
|
+
type: error.type
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def fail!(*args, &blk)
|
|
205
|
+
if args.is_a?(Hash) && args.key?(:errors)
|
|
206
|
+
error_messages = args.fetch(:errors).map do |error|
|
|
207
|
+
if error.is_a?(Hash) && error.key?(:message)
|
|
208
|
+
error.fetch(:message)
|
|
209
|
+
else
|
|
210
|
+
error
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
else
|
|
214
|
+
error_messages = ["Command failed"]
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
error = ApiMaker::CommandFailedError.new(error_messages)
|
|
218
|
+
error.api_maker_args = args
|
|
219
|
+
error.api_maker_block = blk
|
|
220
|
+
|
|
221
|
+
raise error
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def succeed!(*args, &blk)
|
|
225
|
+
command.result(*args, &blk)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def inspect
|
|
229
|
+
"#<#{self.class.name}:#{__id__}>"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
def serialized_model(model)
|
|
235
|
+
collection_serializer = ApiMaker::CollectionSerializer.new(
|
|
236
|
+
ability: current_ability,
|
|
237
|
+
api_maker_args: api_maker_args,
|
|
238
|
+
collection: [model],
|
|
239
|
+
model_class: model.class,
|
|
240
|
+
query_params: args&.dig(:query_params)
|
|
241
|
+
)
|
|
242
|
+
collection_serializer.result
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def serialized_resource(model)
|
|
246
|
+
ApiMaker::Serializer.new(ability: current_ability, api_maker_args: api_maker_args, model: model)
|
|
247
|
+
end
|
|
248
|
+
end
|