forest_admin_rpc_agent 1.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 (30) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/bin/console +11 -0
  4. data/bin/forest_admin_rpc_agent +9 -0
  5. data/bin/setup +8 -0
  6. data/forest_admin_rpc_agent.gemspec +36 -0
  7. data/lib/forest_admin_rpc_agent/agent.rb +19 -0
  8. data/lib/forest_admin_rpc_agent/engine.rb +17 -0
  9. data/lib/forest_admin_rpc_agent/extensions/config_loader.rb +12 -0
  10. data/lib/forest_admin_rpc_agent/extensions/sinatra_extension.rb +32 -0
  11. data/lib/forest_admin_rpc_agent/facades/container.rb +29 -0
  12. data/lib/forest_admin_rpc_agent/middleware/authentication.rb +67 -0
  13. data/lib/forest_admin_rpc_agent/routes/action_execute.rb +31 -0
  14. data/lib/forest_admin_rpc_agent/routes/action_form.rb +49 -0
  15. data/lib/forest_admin_rpc_agent/routes/aggregate.rb +34 -0
  16. data/lib/forest_admin_rpc_agent/routes/base_route.rb +52 -0
  17. data/lib/forest_admin_rpc_agent/routes/chart.rb +30 -0
  18. data/lib/forest_admin_rpc_agent/routes/create.rb +23 -0
  19. data/lib/forest_admin_rpc_agent/routes/datasource_chart.rb +23 -0
  20. data/lib/forest_admin_rpc_agent/routes/delete.rb +28 -0
  21. data/lib/forest_admin_rpc_agent/routes/health_route.rb +13 -0
  22. data/lib/forest_admin_rpc_agent/routes/list.rb +29 -0
  23. data/lib/forest_admin_rpc_agent/routes/schema.rb +24 -0
  24. data/lib/forest_admin_rpc_agent/routes/sse.rb +80 -0
  25. data/lib/forest_admin_rpc_agent/routes/update.rb +26 -0
  26. data/lib/forest_admin_rpc_agent/sse_streamer.rb +14 -0
  27. data/lib/forest_admin_rpc_agent/thor/install.rb +136 -0
  28. data/lib/forest_admin_rpc_agent/version.rb +3 -0
  29. data/lib/forest_admin_rpc_agent.rb +33 -0
  30. metadata +122 -0
@@ -0,0 +1,80 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminRpcAgent
4
+ module Routes
5
+ class Sse
6
+ def initialize(url = 'rpc/sse', method = 'get', name = 'rpc_sse')
7
+ @url = url
8
+ @method = method
9
+ @name = name
10
+ end
11
+
12
+ def registered(app)
13
+ if defined?(Sinatra) && (app == Sinatra::Base || app.ancestors.include?(Sinatra::Base))
14
+ register_sinatra(app)
15
+ elsif defined?(Rails) && app.is_a?(ActionDispatch::Routing::Mapper)
16
+ register_rails(app)
17
+ else
18
+ raise NotImplementedError,
19
+ "Unsupported application type: #{app.class}. #{self} works with Sinatra::Base or ActionDispatch::Routing::Mapper."
20
+ end
21
+ end
22
+
23
+ def register_sinatra(_app)
24
+ # TODO
25
+ end
26
+
27
+ def register_rails(router)
28
+ handler = proc do |hash|
29
+ request = ActionDispatch::Request.new(hash)
30
+ auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
31
+ status, headers, response = auth_middleware.call(request.env)
32
+
33
+ if status == 200
34
+ headers = {
35
+ 'Content-Type' => 'text/event-stream',
36
+ 'Cache-Control' => 'no-cache',
37
+ 'Connection' => 'keep-alive'
38
+ }
39
+
40
+ should_continue = true
41
+ # Intercept CTRL+C
42
+ stop_proc = proc { should_continue = false }
43
+ original_handler = trap('INT', stop_proc)
44
+
45
+ body = Enumerator.new do |yielder|
46
+ stream = SseStreamer.new(yielder)
47
+
48
+ begin
49
+ while should_continue
50
+ # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] heartbeat')
51
+ stream.write('', event: 'heartbeat')
52
+ sleep 1
53
+ end
54
+ rescue IOError
55
+ # Client disconnected
56
+ # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] disconnected')
57
+ ensure
58
+ trap('INT', original_handler)
59
+ stream.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
60
+ # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] stopped streaming')
61
+ yielder.close if yielder.respond_to?(:close)
62
+ end
63
+ end
64
+
65
+ [status, headers, body]
66
+ else
67
+ [status, headers, response]
68
+ end
69
+ end
70
+
71
+ router.match @url,
72
+ defaults: { format: 'event-stream' },
73
+ to: handler,
74
+ via: @method,
75
+ as: @name,
76
+ route_alias: @name
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminRpcAgent
4
+ module Routes
5
+ class Update < BaseRoute
6
+ include ForestAdminDatasourceToolkit::Components::Query
7
+
8
+ def initialize
9
+ super('rpc/:collection_name/update', 'post', 'rpc_update')
10
+ end
11
+
12
+ def handle_request(args)
13
+ return '{}' unless args[:params]['collection_name']
14
+
15
+ caller = ForestAdminDatasourceToolkit::Components::Caller.new(
16
+ **args[:params]['caller'].to_h.transform_keys(&:to_sym)
17
+ )
18
+ datasource = ForestAdminRpcAgent::Facades::Container.datasource
19
+ collection = datasource.get_collection(args[:params]['collection_name'])
20
+ filter = FilterFactory.from_plain_object(args[:params]['filter'])
21
+
22
+ collection.update(caller, filter, args[:params]['data']).to_json
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+
3
+ module ForestAdminRpcAgent
4
+ class SseStreamer
5
+ def initialize(yielder)
6
+ @yielder = yielder
7
+ end
8
+
9
+ def write(object, event: nil)
10
+ @yielder << "event: #{event}\n" if event
11
+ @yielder << "data: #{JSON.dump(object)}\n\n"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,136 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+
4
+ module ForestAdminRpcAgent
5
+ module Thor
6
+ class Install < ::Thor
7
+ include ::Thor::Actions
8
+ # Run the command:
9
+ # for a rails app : forest_admin_rpc_agent install AUTH_SECRET_FROM_YOUR_FOREST_APP
10
+ # for a sinatra app : forest_admin_rpc_agent install AUTH_SECRET_FROM_YOUR_FOREST_APP --app_file=app.rb
11
+
12
+ desc 'install AUTH_SECRET_FROM_YOUR_FOREST_APP',
13
+ 'Install ForestAdmin RPC Agent by generating necessary configuration and files'
14
+ method_option :app_file, type: :string, required: false, desc: 'Main file of the Sinatra application (ex: app.rb)'
15
+
16
+ RAILS_CONFIG_PATH = 'config/initializers/forest_admin_rpc_agent.rb'.freeze
17
+ RAILS_AGENT_PATH = 'app/lib/forest_admin_rpc_agent/create_rpc_agent.rb'.freeze
18
+
19
+ SINATRA_CONFIG_PATH = 'config/forest_admin_rpc_agent.rb'.freeze
20
+ SINATRA_AGENT_PATH = 'config/create_rpc_agent.rb'.freeze
21
+
22
+ def install(auth_secret)
23
+ if rails_app?
24
+ say_status('info', 'Rails framework detected ✅', :green)
25
+ setup_rails(auth_secret)
26
+ elsif sinatra_app?
27
+ if options[:app_file].nil?
28
+ say_status('error', 'You must specify the main file of the Sinatra application with --app_file', :red)
29
+ raise ::Thor::Error, 'You must specify the main file of the Sinatra application with --app_file'
30
+ end
31
+ say_status('info', 'Sinatra framework detected ✅', :green)
32
+ setup_sinatra(auth_secret)
33
+ else
34
+ say_status('error', 'Unsupported framework', :red)
35
+ raise ::Thor::Error, 'Unsupported framework, only Rails and Sinatra are supported with ForestAdmin RPC Agent'
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def setup_rails(auth_secret)
42
+ require 'rails/generators'
43
+ require 'rails/generators/actions'
44
+
45
+ create_config_files(auth_secret, RAILS_CONFIG_PATH, RAILS_AGENT_PATH)
46
+
47
+ klass = Class.new(Rails::Generators::Base) do
48
+ include Rails::Generators::Actions
49
+ end
50
+ klass.new.route("mount ForestAdminRpcAgent::Engine => '/forest'")
51
+
52
+ say_status('success', 'ForestAdmin RPC Agent installed on Rails ✅', :green)
53
+ end
54
+
55
+ def setup_sinatra(auth_secret)
56
+ create_config_files(auth_secret, SINATRA_CONFIG_PATH, SINATRA_AGENT_PATH)
57
+
58
+ app_file_content = File.read(options[:app_file])
59
+ if app_file_content.include?("require 'sinatra'")
60
+ insert_into_file options[:app_file], <<~RUBY, after: "require 'sinatra'\n"
61
+ require_relative 'config/forest_admin_rpc_agent'
62
+ require 'forest_admin_rpc_agent/extensions/sinatra_extension'
63
+ RUBY
64
+
65
+ say_status('success', 'ForestAdmin RPC Agent installed on Sinatra ✅', :green)
66
+ else
67
+ say_status('error', "Could not find `require 'sinatra'` in #{options[:app_file]}", :red)
68
+ raise ::Thor::Error, "Please add `require 'sinatra'` in #{options[:app_file]} before running this command."
69
+ end
70
+ end
71
+
72
+ def create_config_files(auth_secret, config_path, agent_path)
73
+ # Create necessary directories
74
+ FileUtils.mkdir_p(File.dirname(config_path))
75
+ FileUtils.mkdir_p(File.dirname(agent_path))
76
+
77
+ # Create configuration file
78
+ create_file config_path, <<~RUBY
79
+ ForestAdminRpcAgent.configure do |config|
80
+ config.auth_secret = '#{auth_secret}'
81
+ end
82
+ RUBY
83
+
84
+ # Create agent setup file
85
+ create_file agent_path, <<~RUBY
86
+ # This file contains code to create and configure your Forest Admin agent
87
+ # You can customize this file according to your needs
88
+
89
+ module ForestAdminRpcAgent
90
+ class CreateRpcAgent
91
+ def self.setup!
92
+ # Initialize your agent here
93
+ end
94
+ end
95
+ end
96
+ RUBY
97
+ end
98
+
99
+ def rails_app?
100
+ return true if Object.const_defined?(:Rails) && Rails.respond_to?(:root)
101
+
102
+ File.exist?('config/application.rb') && Dir.exist?('app/controllers')
103
+ end
104
+
105
+ def sinatra_app?
106
+ # 1. Check if Sinatra is already loaded in memory
107
+ return true if defined?(Sinatra::Base)
108
+
109
+ # 2. Check Gemfile.lock
110
+ return true if File.exist?('Gemfile.lock') && File.read('Gemfile.lock').include?('sinatra')
111
+
112
+ # 3. Check config.ru
113
+ return true if File.exist?('config.ru') && File.read('config.ru') =~ %r{require ['"]sinatra(/base)?['"]}
114
+
115
+ # 4. Look for a class that inherits from Sinatra in Ruby files
116
+ Dir.glob('*.rb').any? do |file|
117
+ content = File.read(file)
118
+ content.match?(%r{require ['"](sinatra|sinatra/base)['"]}) &&
119
+ (content.include?('< Sinatra::') || content.include?('Sinatra::Application'))
120
+ end
121
+ end
122
+
123
+ class << self
124
+ private
125
+
126
+ def exit_on_failure?
127
+ true
128
+ end
129
+
130
+ def source_root
131
+ File.dirname(__FILE__)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,3 @@
1
+ module ForestAdminRpcAgent
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'dry-configurable'
2
+ require 'forest_admin_rpc_agent/version'
3
+ require 'forest_admin_rpc_agent/engine' if defined?(Rails)
4
+ require 'forest_admin_agent'
5
+ require 'forest_admin_datasource_customizer'
6
+ require 'forest_admin_datasource_toolkit'
7
+ require 'zeitwerk'
8
+
9
+ loader = Zeitwerk::Loader.for_gem
10
+ loader.setup
11
+
12
+ module ForestAdminRpcAgent
13
+ extend Dry::Configurable
14
+
15
+ setting :debug, default: true
16
+ setting :auth_secret
17
+ setting :env_secret
18
+ setting :forest_server_url, default: 'https://api.forestadmin.com'
19
+ setting :is_production, default: false
20
+ setting :prefix, default: nil
21
+ setting :cache_dir, default: :'tmp/cache/forest_admin'
22
+ setting :project_dir, default: Dir.pwd
23
+ setting :loggerLevel, default: 'info'
24
+ setting :logger, default: nil
25
+ setting :customize_error_message, default: nil
26
+
27
+ begin
28
+ require 'thor'
29
+ require 'forest_admin_rpc_agent/thor/install'
30
+ rescue LoadError
31
+ # Thor is not available, skip loading commands
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forest_admin_rpc_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthieu
8
+ - Nicolas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2025-09-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: dry-configurable
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: zeitwerk
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '2.3'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.3'
42
+ - !ruby/object:Gem::Dependency
43
+ name: thor
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.3'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.3'
56
+ description: |-
57
+ Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
58
+ admin work on any Ruby application.
59
+ email:
60
+ - matthv@gmail.com
61
+ - nicolasalexandre9@gmail.com
62
+ executables:
63
+ - forest_admin_rpc_agent
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - LICENSE
68
+ - bin/console
69
+ - bin/forest_admin_rpc_agent
70
+ - bin/setup
71
+ - forest_admin_rpc_agent.gemspec
72
+ - lib/forest_admin_rpc_agent.rb
73
+ - lib/forest_admin_rpc_agent/agent.rb
74
+ - lib/forest_admin_rpc_agent/engine.rb
75
+ - lib/forest_admin_rpc_agent/extensions/config_loader.rb
76
+ - lib/forest_admin_rpc_agent/extensions/sinatra_extension.rb
77
+ - lib/forest_admin_rpc_agent/facades/container.rb
78
+ - lib/forest_admin_rpc_agent/middleware/authentication.rb
79
+ - lib/forest_admin_rpc_agent/routes/action_execute.rb
80
+ - lib/forest_admin_rpc_agent/routes/action_form.rb
81
+ - lib/forest_admin_rpc_agent/routes/aggregate.rb
82
+ - lib/forest_admin_rpc_agent/routes/base_route.rb
83
+ - lib/forest_admin_rpc_agent/routes/chart.rb
84
+ - lib/forest_admin_rpc_agent/routes/create.rb
85
+ - lib/forest_admin_rpc_agent/routes/datasource_chart.rb
86
+ - lib/forest_admin_rpc_agent/routes/delete.rb
87
+ - lib/forest_admin_rpc_agent/routes/health_route.rb
88
+ - lib/forest_admin_rpc_agent/routes/list.rb
89
+ - lib/forest_admin_rpc_agent/routes/schema.rb
90
+ - lib/forest_admin_rpc_agent/routes/sse.rb
91
+ - lib/forest_admin_rpc_agent/routes/update.rb
92
+ - lib/forest_admin_rpc_agent/sse_streamer.rb
93
+ - lib/forest_admin_rpc_agent/thor/install.rb
94
+ - lib/forest_admin_rpc_agent/version.rb
95
+ homepage: https://www.forestadmin.com
96
+ licenses:
97
+ - GPL-3.0
98
+ metadata:
99
+ homepage_uri: https://www.forestadmin.com
100
+ source_code_uri: https://github.com/ForestAdmin/agent-ruby
101
+ changelog_uri: https://github.com/ForestAdmin/agent-ruby/blob/main/CHANGELOG.md
102
+ rubygems_mfa_required: 'false'
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 3.0.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.4.20
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Ruby agent for Forest Admin.
122
+ test_files: []