api_maker 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|