api_maker 0.0.1

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