api_maker 0.0.1

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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +476 -0
  4. data/Rakefile +27 -0
  5. data/app/channels/api_maker/subscriptions_channel.rb +80 -0
  6. data/app/controllers/api_maker/base_controller.rb +32 -0
  7. data/app/controllers/api_maker/commands_controller.rb +26 -0
  8. data/app/controllers/api_maker/devise_controller.rb +60 -0
  9. data/app/controllers/api_maker/session_statuses_controller.rb +33 -0
  10. data/app/services/api_maker/application_service.rb +7 -0
  11. data/app/services/api_maker/collection_command_service.rb +24 -0
  12. data/app/services/api_maker/command_response.rb +67 -0
  13. data/app/services/api_maker/command_service.rb +31 -0
  14. data/app/services/api_maker/create_command.rb +62 -0
  15. data/app/services/api_maker/create_command_service.rb +18 -0
  16. data/app/services/api_maker/destroy_command.rb +39 -0
  17. data/app/services/api_maker/destroy_command_service.rb +22 -0
  18. data/app/services/api_maker/generate_react_native_api_service.rb +61 -0
  19. data/app/services/api_maker/index_command.rb +96 -0
  20. data/app/services/api_maker/index_command_service.rb +22 -0
  21. data/app/services/api_maker/js_method_namer_service.rb +11 -0
  22. data/app/services/api_maker/member_command_service.rb +25 -0
  23. data/app/services/api_maker/model_content_generator_service.rb +108 -0
  24. data/app/services/api_maker/models_finder_service.rb +22 -0
  25. data/app/services/api_maker/models_generator_service.rb +104 -0
  26. data/app/services/api_maker/update_command.rb +43 -0
  27. data/app/services/api_maker/update_command_service.rb +21 -0
  28. data/app/services/api_maker/valid_command.rb +35 -0
  29. data/app/services/api_maker/valid_command_service.rb +21 -0
  30. data/app/views/api_maker/_data.html.erb +15 -0
  31. data/config/rails_best_practices.yml +55 -0
  32. data/config/routes.rb +7 -0
  33. data/lib/api_maker.rb +36 -0
  34. data/lib/api_maker/ability.rb +39 -0
  35. data/lib/api_maker/ability_loader.rb +21 -0
  36. data/lib/api_maker/action_controller_base_extensions.rb +5 -0
  37. data/lib/api_maker/base_command.rb +81 -0
  38. data/lib/api_maker/base_resource.rb +78 -0
  39. data/lib/api_maker/collection_serializer.rb +69 -0
  40. data/lib/api_maker/command_spec_helper.rb +57 -0
  41. data/lib/api_maker/configuration.rb +34 -0
  42. data/lib/api_maker/engine.rb +5 -0
  43. data/lib/api_maker/individual_command.rb +37 -0
  44. data/lib/api_maker/javascript/api.js +92 -0
  45. data/lib/api_maker/javascript/base-model.js +543 -0
  46. data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +16 -0
  47. data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +47 -0
  48. data/lib/api_maker/javascript/bootstrap/card.jsx +79 -0
  49. data/lib/api_maker/javascript/bootstrap/checkbox.jsx +127 -0
  50. data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +105 -0
  51. data/lib/api_maker/javascript/bootstrap/live-table.jsx +168 -0
  52. data/lib/api_maker/javascript/bootstrap/money-input.jsx +136 -0
  53. data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +80 -0
  54. data/lib/api_maker/javascript/bootstrap/select.jsx +168 -0
  55. data/lib/api_maker/javascript/bootstrap/string-input.jsx +203 -0
  56. data/lib/api_maker/javascript/cable-connection-pool.js +169 -0
  57. data/lib/api_maker/javascript/cable-subscription-pool.js +111 -0
  58. data/lib/api_maker/javascript/cable-subscription.js +33 -0
  59. data/lib/api_maker/javascript/collection.js +186 -0
  60. data/lib/api_maker/javascript/commands-pool.js +123 -0
  61. data/lib/api_maker/javascript/custom-error.js +14 -0
  62. data/lib/api_maker/javascript/deserializer.js +35 -0
  63. data/lib/api_maker/javascript/devise.js.erb +113 -0
  64. data/lib/api_maker/javascript/error-logger.js +119 -0
  65. data/lib/api_maker/javascript/event-connection.jsx +24 -0
  66. data/lib/api_maker/javascript/event-created.jsx +26 -0
  67. data/lib/api_maker/javascript/event-destroyed.jsx +26 -0
  68. data/lib/api_maker/javascript/event-emitter-listener.jsx +32 -0
  69. data/lib/api_maker/javascript/event-listener.jsx +41 -0
  70. data/lib/api_maker/javascript/event-updated.jsx +26 -0
  71. data/lib/api_maker/javascript/form-data-to-object.js +70 -0
  72. data/lib/api_maker/javascript/included.js +39 -0
  73. data/lib/api_maker/javascript/key-value-store.js +47 -0
  74. data/lib/api_maker/javascript/logger.js +23 -0
  75. data/lib/api_maker/javascript/model-name.js +21 -0
  76. data/lib/api_maker/javascript/model-template.js.erb +110 -0
  77. data/lib/api_maker/javascript/models-response-reader.js +43 -0
  78. data/lib/api_maker/javascript/paginate.jsx +128 -0
  79. data/lib/api_maker/javascript/params.js +68 -0
  80. data/lib/api_maker/javascript/resource-route.jsx +75 -0
  81. data/lib/api_maker/javascript/resource-routes.jsx +36 -0
  82. data/lib/api_maker/javascript/result.js +25 -0
  83. data/lib/api_maker/javascript/session-status-updater.js +113 -0
  84. data/lib/api_maker/javascript/sort-link.jsx +88 -0
  85. data/lib/api_maker/javascript/updated-attribute.jsx +60 -0
  86. data/lib/api_maker/loader.rb +14 -0
  87. data/lib/api_maker/memory_storage.rb +65 -0
  88. data/lib/api_maker/model_extensions.rb +96 -0
  89. data/lib/api_maker/permitted_params_argument.rb +12 -0
  90. data/lib/api_maker/preloader.rb +91 -0
  91. data/lib/api_maker/preloader_belongs_to.rb +58 -0
  92. data/lib/api_maker/preloader_has_many.rb +69 -0
  93. data/lib/api_maker/preloader_has_one.rb +70 -0
  94. data/lib/api_maker/preloader_through.rb +101 -0
  95. data/lib/api_maker/railtie.rb +14 -0
  96. data/lib/api_maker/relationship_includer.rb +42 -0
  97. data/lib/api_maker/resource_routing.rb +8 -0
  98. data/lib/api_maker/result_parser.rb +50 -0
  99. data/lib/api_maker/serializer.rb +86 -0
  100. data/lib/api_maker/spec_helper.rb +100 -0
  101. data/lib/api_maker/version.rb +3 -0
  102. data/lib/tasks/api_maker_tasks.rake +5 -0
  103. metadata +581 -0
@@ -0,0 +1,27 @@
1
+ begin
2
+ require "bundler/setup"
3
+ rescue LoadError
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
+ end
6
+
7
+ require "rdoc/task"
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = "rdoc"
11
+ rdoc.title = "ApiMaker"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.md")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load "rails/tasks/engine.rake"
19
+
20
+ load "rails/tasks/statistics.rake"
21
+
22
+ require "bundler/gem_tasks"
23
+
24
+ if Rails.env.development? || Rails.env.test?
25
+ require "best_practice_project"
26
+ BestPracticeProject.load_tasks
27
+ end
@@ -0,0 +1,80 @@
1
+ class ApiMaker::SubscriptionsChannel < ApplicationCable::Channel
2
+ def subscribed
3
+ params[:subscription_data].each do |model_name, subscription_types|
4
+ subscription_types["events"]&.each do |event_name, model_ids|
5
+ connect_event(model_name, model_ids, event_name)
6
+ end
7
+
8
+ connect_creates(model_name) if subscription_types.key?("creates")
9
+
10
+ if subscription_types.key?("updates")
11
+ model_ids = subscription_types.fetch("updates")
12
+ connect_updates(model_name, model_ids)
13
+ end
14
+
15
+ if subscription_types.key?("destroys")
16
+ model_ids = subscription_types.fetch("destroys")
17
+ connect_destroys(model_name, model_ids)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def connect_creates(model_name)
25
+ model_class = model_for_resource_name(model_name)
26
+ channel_name = model_class.api_maker_broadcast_create_channel_name
27
+ stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
28
+ # We need to look the model up to evaluate if the user has access
29
+ model = data.fetch("model_class_name").safe_constantize.accessible_by(current_ability, :create_events).find(data.fetch("model_id"))
30
+
31
+ # Transmit the data to JS if its allowed
32
+ transmit data if model
33
+ end
34
+ end
35
+
36
+ def connect_destroys(model_name, model_ids)
37
+ model_class = model_for_resource_name(model_name)
38
+ models = model_class.accessible_by(current_ability, :destroy_events).where(model_class.primary_key => model_ids)
39
+ models.each do |model|
40
+ channel_name = model.api_maker_broadcast_destroy_channel_name
41
+ stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
42
+ transmit data
43
+ end
44
+ end
45
+ end
46
+
47
+ def connect_event(model_name, model_ids, event_name)
48
+ ability_name = "event_#{event_name}".to_sym
49
+ model_class = model_for_resource_name(model_name)
50
+ models = model_class.accessible_by(current_ability, ability_name).where(model_class.primary_key => model_ids)
51
+ models.each do |model|
52
+ channel_name = model.api_maker_event_channel_name(event_name)
53
+ stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
54
+ transmit data
55
+ end
56
+ end
57
+ end
58
+
59
+ def connect_updates(model_name, model_ids)
60
+ model_class = model_for_resource_name(model_name)
61
+ models = model_class.accessible_by(current_ability, :update_events).where(model_class.primary_key => model_ids)
62
+ models.each do |model|
63
+ channel_name = model.api_maker_broadcast_update_channel_name
64
+ stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
65
+ transmit data
66
+ end
67
+ end
68
+ end
69
+
70
+ def model_for_resource_name(resource_name)
71
+ resource_for_resource_name(resource_name).model_class
72
+ end
73
+
74
+ def resource_for_resource_name(resource_name)
75
+ resource = "Resources::#{resource_name}Resource".safe_constantize
76
+ raise "Cannot find resource by resource name: #{resource_name}" unless resource
77
+
78
+ resource
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ class ApiMaker::BaseController < ApplicationController
2
+ protect_from_forgery with: :exception
3
+
4
+ before_action :set_locale
5
+
6
+ rescue_from Exception, with: :render_error
7
+
8
+ private
9
+
10
+ def current_ability
11
+ @current_ability ||= ::ApiMaker::Ability.new(args: api_maker_args)
12
+ end
13
+
14
+ def render_error(error)
15
+ puts error.inspect
16
+ puts error.backtrace
17
+
18
+ logger.error error.inspect
19
+ logger.error error.backtrace.join("\n")
20
+
21
+ raise error
22
+ end
23
+
24
+ def set_locale
25
+ return if session[:locale].blank?
26
+
27
+ locale = session[:locale].to_s
28
+ locale_exists = I18n.available_locales.map(&:to_s).include?(locale)
29
+
30
+ I18n.locale = locale if locale_exists
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ class ApiMaker::CommandsController < ApiMaker::BaseController
2
+ def create
3
+ command_response = ApiMaker::CommandResponse.new(controller: self)
4
+ controller = self
5
+
6
+ params[:pool].each do |command_type, command_type_data|
7
+ command_type_data.each do |resource_plural_name, command_model_data|
8
+ command_model_data.each do |command_name, command_data|
9
+ ApiMaker.const_get("#{command_type.camelize}CommandService").execute!(
10
+ ability: current_ability,
11
+ args: api_maker_args,
12
+ command_response: command_response,
13
+ commands: command_data,
14
+ command_name: command_name,
15
+ resource_name: resource_plural_name,
16
+ controller: controller
17
+ )
18
+ end
19
+ end
20
+ end
21
+
22
+ command_response.join_threads
23
+
24
+ render json: {responses: command_response.result}
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ class ApiMaker::DeviseController < ApiMaker::BaseController
2
+ include Devise::Controllers::Rememberable
3
+
4
+ before_action :check_model_exists, only: :do_sign_in
5
+ before_action :check_serializer_exists, only: :do_sign_in
6
+
7
+ def do_sign_in
8
+ if !model.active_for_authentication?
9
+ render json: {success: false, errors: [model.inactive_message]}
10
+ elsif model.valid_password?(params[:password])
11
+ sign_in(model, scope: scope)
12
+ remember_me(model) if params.dig(:args, :rememberMe)
13
+ render json: {success: true, model_data: serializer.result}
14
+ else
15
+ render json: {success: false, errors: [invalid_error_message]}
16
+ end
17
+ end
18
+
19
+ def do_sign_out
20
+ scope = params.dig(:args, :scope).presence || "user"
21
+ current_model = __send__("current_#{scope}")
22
+ sign_out current_model
23
+ render json: {success: true}
24
+ end
25
+
26
+ private
27
+
28
+ def check_model_exists
29
+ error_msg = t("devise.failure.not_found_in_database", authentication_keys: model_class.authentication_keys.join(", "))
30
+ render json: {success: false, errors: [error_msg]} unless model
31
+ end
32
+
33
+ def check_serializer_exists
34
+ render json: {success: false, errors: ["Serializer doesn't exist for #{scope}"]} unless resource
35
+ end
36
+
37
+ def invalid_error_message
38
+ t("devise.failure.invalid", authentication_keys: model_class.authentication_keys.join(", "))
39
+ end
40
+
41
+ def model
42
+ @model ||= model_class.find_for_authentication(email: params[:username])
43
+ end
44
+
45
+ def model_class
46
+ @model_class ||= scope.camelize.safe_constantize
47
+ end
48
+
49
+ def scope
50
+ @scope ||= params.dig(:args, :scope).presence || "user"
51
+ end
52
+
53
+ def resource
54
+ @resource ||= ApiMaker::Serializer.resource_for(model.class)
55
+ end
56
+
57
+ def serializer
58
+ @serializer ||= ApiMaker::Serializer.new(ability: current_ability, args: api_maker_args, model: model)
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ class ApiMaker::SessionStatusesController < ActionController::Base
2
+ skip_before_action :verify_authenticity_token, raise: false
3
+
4
+ def create
5
+ scopes = {}
6
+ result = {
7
+ devise: {
8
+ timeout_in: Devise.timeout_in.to_i
9
+ },
10
+ csrf_token: form_authenticity_token,
11
+ scopes: scopes
12
+ }
13
+
14
+ Devise.mappings.each do |scope|
15
+ klass = scope[1].class_name.safe_constantize
16
+ param_key = klass.model_name.param_key
17
+
18
+ model_method_name = "current_#{param_key}"
19
+ model = __send__(model_method_name)
20
+ model_pk = model&.__send__(klass.primary_key)
21
+
22
+ signed_in_method_name = "#{param_key}_signed_in?"
23
+ signed_in = __send__(signed_in_method_name)
24
+
25
+ scopes[param_key] = {
26
+ primary_key: model_pk,
27
+ signed_in: signed_in
28
+ }
29
+ end
30
+
31
+ render json: result
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ class ApiMaker::ApplicationService < ServicePattern::Service
2
+ def api_maker_json(object)
3
+ json = object.to_json
4
+ json.gsub!(/"\{\{(.+?)\}\}"/, "\\1")
5
+ json
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ class ApiMaker::CollectionCommandService < ApiMaker::CommandService
2
+ def execute
3
+ authorize!
4
+
5
+ constant.execute_in_thread!(
6
+ ability: ability,
7
+ args: args,
8
+ collection: nil,
9
+ commands: commands,
10
+ command_response: command_response,
11
+ controller: controller
12
+ )
13
+
14
+ ServicePattern::Response.new(success: true)
15
+ end
16
+
17
+ def authorize!
18
+ raise CanCan::AccessDenied, "No access to '#{@command_name}' on '#{model_class.name}'" unless @ability.can?(@command_name.to_sym, model_class)
19
+ end
20
+
21
+ def constant
22
+ @constant ||= "Commands::#{namespace}::#{@command_name.camelize}".constantize
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ class ApiMaker::CommandResponse
2
+ attr_reader :controller, :locale, :result
3
+
4
+ def initialize(controller:)
5
+ @controller = controller
6
+ @locale = I18n.locale
7
+ @mutex = Mutex.new
8
+ @result = {}
9
+ @threads = []
10
+ end
11
+
12
+ def error_for_command(id, data)
13
+ respond_to_command(id, data, :error)
14
+ end
15
+
16
+ def fail_for_command(id, data)
17
+ respond_to_command(id, data, :failed)
18
+ end
19
+
20
+ def result_for_command(id, data)
21
+ respond_to_command(id, data, :success)
22
+ end
23
+
24
+ def join_threads
25
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
26
+ @threads.each(&:join)
27
+ end
28
+ end
29
+
30
+ def respond_to_command(id, data, type)
31
+ @mutex.synchronize do
32
+ @result[id] = {type: type, data: data}
33
+ end
34
+ end
35
+
36
+ def threadding?
37
+ ApiMaker::Configuration.current.threadding
38
+ end
39
+
40
+ def with_thread(&blk)
41
+ if Rails.env.test? || !threadding?
42
+ yield
43
+ else
44
+ spawn_thread(&blk)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def spawn_thread
51
+ @threads << Thread.new do
52
+ Rails.application.executor.wrap do
53
+ I18n.with_locale(locale) do
54
+ yield
55
+ end
56
+ end
57
+ rescue => e # rubocop:disable Style/RescueStandardError
58
+ puts e.inspect
59
+ puts e.backtrace
60
+
61
+ Rails.logger.error e.message
62
+ Rails.logger.error e.backtrace.join("\n")
63
+
64
+ ApiMaker::Configuration.current.report_error(controller: controller, error: e)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ class ApiMaker::CommandService < ApiMaker::ApplicationService
2
+ attr_reader :ability, :args, :commands, :command_name, :command_response, :controller, :resource_name
3
+
4
+ def initialize(ability:, args:, commands:, command_name:, command_response:, controller:, resource_name:)
5
+ @ability = ability
6
+ @args = args
7
+ @command_name = command_name
8
+ @command_response = command_response
9
+ @commands = commands
10
+ @controller = controller
11
+ @resource_name = resource_name
12
+ end
13
+
14
+ def namespace
15
+ @namespace ||= resource_name.underscore.camelize
16
+ end
17
+
18
+ def resource
19
+ @resource ||= begin
20
+ resource_class_name = "Resources::#{resource_name.underscore.singularize.camelize}Resource"
21
+ resource = resource_class_name.safe_constantize
22
+ raise "Couldnt find resource from resource name: #{resource_class_name}" unless resource
23
+
24
+ resource
25
+ end
26
+ end
27
+
28
+ def model_class
29
+ @model_class ||= resource.model_class
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ class ApiMaker::CreateCommand < ApiMaker::BaseCommand
2
+ attr_reader :command, :model, :params, :serializer
3
+
4
+ def execute!
5
+ each_command do |command|
6
+ @command = command
7
+ @model = collection.klass.new
8
+ @params = command.args || {}
9
+ @serializer = serialized_resource(model)
10
+ @model.assign_attributes(sanitize_parameters)
11
+
12
+ if !current_ability.can?(:create, @model)
13
+ failure_response(["No access to create that resource"])
14
+ elsif @model.save
15
+ success_response
16
+ else
17
+ failure_response(model.errors.full_messages)
18
+ end
19
+ end
20
+
21
+ ServicePattern::Response.new(success: true)
22
+ end
23
+
24
+ def api_maker_resource_class
25
+ @api_maker_resource_class ||= "Resources::#{collection.klass.name}Resource".constantize
26
+ end
27
+
28
+ def failure_response(errors)
29
+ command.fail(
30
+ model: serializer.result,
31
+ success: false,
32
+ errors: errors
33
+ )
34
+ end
35
+
36
+ def resource_instance_class_name
37
+ @resource_instance_class_name ||= self.class.name.split("::").last.gsub(/Controller$/, "").singularize
38
+ end
39
+
40
+ def resource_instance_class
41
+ @resource_instance_class ||= api_maker_resource_class.model_class
42
+ end
43
+
44
+ def resource_variable_name
45
+ @resource_variable_name ||= resource_instance_class_name.underscore.parameterize
46
+ end
47
+
48
+ def sanitize_parameters
49
+ serializer.resource_instance.permitted_params(ApiMaker::PermittedParamsArgument.new(command: command, model: model))
50
+ end
51
+
52
+ def serialized_resource(model)
53
+ ApiMaker::Serializer.new(ability: current_ability, args: api_maker_args, model: model)
54
+ end
55
+
56
+ def success_response
57
+ command.result(
58
+ model: serializer.result,
59
+ success: true
60
+ )
61
+ end
62
+ end