munster 0.1.0

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +14 -0
  3. data/.standard.yml +3 -0
  4. data/CHANGELOG.md +3 -0
  5. data/LICENSE +21 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +39 -0
  8. data/Rakefile +18 -0
  9. data/config/routes.rb +3 -0
  10. data/example/.gitattributes +7 -0
  11. data/example/.gitignore +29 -0
  12. data/example/.ruby-version +1 -0
  13. data/example/Gemfile +32 -0
  14. data/example/Gemfile.lock +208 -0
  15. data/example/README.md +24 -0
  16. data/example/Rakefile +8 -0
  17. data/example/app/assets/images/.keep +0 -0
  18. data/example/app/assets/stylesheets/application.css +1 -0
  19. data/example/app/controllers/application_controller.rb +4 -0
  20. data/example/app/controllers/concerns/.keep +0 -0
  21. data/example/app/helpers/application_helper.rb +4 -0
  22. data/example/app/models/application_record.rb +5 -0
  23. data/example/app/models/concerns/.keep +0 -0
  24. data/example/app/views/layouts/application.html.erb +15 -0
  25. data/example/app/webhooks/test_handler.rb +28 -0
  26. data/example/bin/bundle +109 -0
  27. data/example/bin/rails +4 -0
  28. data/example/bin/rake +4 -0
  29. data/example/bin/setup +33 -0
  30. data/example/config/application.rb +39 -0
  31. data/example/config/boot.rb +5 -0
  32. data/example/config/credentials.yml.enc +1 -0
  33. data/example/config/database.yml +25 -0
  34. data/example/config/environment.rb +7 -0
  35. data/example/config/environments/development.rb +61 -0
  36. data/example/config/environments/production.rb +71 -0
  37. data/example/config/environments/test.rb +52 -0
  38. data/example/config/initializers/assets.rb +14 -0
  39. data/example/config/initializers/content_security_policy.rb +27 -0
  40. data/example/config/initializers/filter_parameter_logging.rb +10 -0
  41. data/example/config/initializers/generators.rb +7 -0
  42. data/example/config/initializers/inflections.rb +18 -0
  43. data/example/config/initializers/munster.rb +7 -0
  44. data/example/config/initializers/permissions_policy.rb +13 -0
  45. data/example/config/locales/en.yml +33 -0
  46. data/example/config/puma.rb +45 -0
  47. data/example/config/routes.rb +5 -0
  48. data/example/config.ru +8 -0
  49. data/example/db/migrate/20240523125859_create_munster_tables.rb +22 -0
  50. data/example/db/schema.rb +24 -0
  51. data/example/db/seeds.rb +9 -0
  52. data/example/lib/assets/.keep +0 -0
  53. data/example/lib/tasks/.keep +0 -0
  54. data/example/log/.keep +0 -0
  55. data/example/public/404.html +67 -0
  56. data/example/public/422.html +67 -0
  57. data/example/public/500.html +66 -0
  58. data/example/public/apple-touch-icon-precomposed.png +0 -0
  59. data/example/public/apple-touch-icon.png +0 -0
  60. data/example/public/favicon.ico +0 -0
  61. data/example/public/robots.txt +1 -0
  62. data/example/test/controllers/.keep +0 -0
  63. data/example/test/fixtures/files/.keep +0 -0
  64. data/example/test/helpers/.keep +0 -0
  65. data/example/test/integration/.keep +0 -0
  66. data/example/test/models/.keep +0 -0
  67. data/example/test/test_helper.rb +15 -0
  68. data/example/tmp/.keep +0 -0
  69. data/example/tmp/pids/.keep +0 -0
  70. data/example/vendor/.keep +0 -0
  71. data/lib/munster/base_handler.rb +67 -0
  72. data/lib/munster/controllers/receive_webhooks_controller.rb +52 -0
  73. data/lib/munster/engine.rb +22 -0
  74. data/lib/munster/install_generator.rb +30 -0
  75. data/lib/munster/jobs/processing_job.rb +14 -0
  76. data/lib/munster/models/received_webhook.rb +23 -0
  77. data/lib/munster/state_machine_enum.rb +125 -0
  78. data/lib/munster/templates/create_munster_tables.rb.erb +23 -0
  79. data/lib/munster/templates/munster.rb +4 -0
  80. data/lib/munster/version.rb +5 -0
  81. data/lib/munster.rb +23 -0
  82. data/lib/tasks/munster_tasks.rake +4 -0
  83. data/sig/munster.rbs +4 -0
  84. metadata +197 -0
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ .rails-default-error-page {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ .rails-default-error-page div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ .rails-default-error-page div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ .rails-default-error-page h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ .rails-default-error-page div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body class="rails-default-error-page">
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ .rails-default-error-page {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ .rails-default-error-page div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ .rails-default-error-page div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ .rails-default-error-page h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ .rails-default-error-page div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body class="rails-default-error-page">
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
File without changes
File without changes
@@ -0,0 +1 @@
1
+ # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["RAILS_ENV"] ||= "test"
4
+ require_relative "../config/environment"
5
+ require "rails/test_help"
6
+
7
+ class ActiveSupport::TestCase
8
+ # Run tests in parallel with specified workers
9
+ parallelize(workers: :number_of_processors)
10
+
11
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
12
+ fixtures :all
13
+
14
+ # Add more helper methods to be used by all tests here...
15
+ end
data/example/tmp/.keep ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jobs/processing_job"
4
+
5
+ module Munster
6
+ class BaseHandler
7
+ class << self
8
+ # Reimplement this method, it's being used in WebhooksController to store incoming webhook.
9
+ # Also que for processing in the end.
10
+ # @return [void]
11
+ def handle(action_dispatch_request)
12
+ binary_body_str = action_dispatch_request.body.read.force_encoding(Encoding::BINARY)
13
+ attrs = {
14
+ body: binary_body_str,
15
+ handler_module_name: name,
16
+ handler_event_id: extract_event_id_from_request(action_dispatch_request)
17
+ }
18
+ webhook = Munster::ReceivedWebhook.create!(**attrs)
19
+
20
+ Munster.configuration.processing_job_class.perform_later(webhook)
21
+ rescue ActiveRecord::RecordNotUnique # Deduplicated
22
+ nil
23
+ end
24
+
25
+ # This method will be used to process webhook by async worker.
26
+ def process(received_webhook)
27
+ end
28
+
29
+ # This should be defined for each webhook handler and should be unique.
30
+ # Otherwise controller will never pick up, that this handler exists.
31
+ #
32
+ # Please consider that this will be used in url, so don't use underscores or any other symbols that are not used in URL.
33
+ def service_id
34
+ :base
35
+ end
36
+
37
+ # This method verifies that request actually comes from provider:
38
+ # signature validation, HTTP authentication, IP whitelisting and the like
39
+ def valid?(action_dispatch_request)
40
+ true
41
+ end
42
+
43
+ # Default implementation just generates UUID, but if the webhook sender sends us
44
+ # an event ID we use it for deduplication.
45
+ def extract_event_id_from_request(action_dispatch_request)
46
+ SecureRandom.uuid
47
+ end
48
+
49
+ # Webhook senders have varying retry behaviors, and often you want to "pretend"
50
+ # everything is fine even though there is an error so that they keep sending you
51
+ # data and do not disable your endpoint forcibly. We allow this to be configured
52
+ # on a per-handler basis - a better webhooks sender will be able to make out
53
+ # some sense of the errors.
54
+ def expose_errors_to_sender?
55
+ false
56
+ end
57
+
58
+ # Tells the controller whether this handler is active or not. This can be used
59
+ # to deactivate a particular handler via feature flags for example, or use other
60
+ # logic to determine whether the handler may be used to create new received webhooks
61
+ # in the system. This is primarily needed for load shedding.
62
+ def active?
63
+ true
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Munster
4
+ class ReceiveWebhooksController < ActionController::API
5
+ class HandlerRefused < StandardError
6
+ end
7
+
8
+ def create
9
+ handler = lookup_handler(params[:service_id])
10
+ return render_error("Webhook handler is inactive", :service_unavailable) unless handler.active?
11
+
12
+ raise HandlerRefused unless handler.valid?(request)
13
+
14
+ # FIXME: Duplicated webhook will be overwritten here and processing job will be quite for second time.
15
+ # This will generate a following error in this case:
16
+ # Error performing Munster::ProcessingJob (Job ID: b40f3f28-81be-4c99-bce8-9ad879ec9754) from Async(default) in 9.95ms: ActiveRecord::RecordInvalid (Validation failed: Status Invalid transition from processing to received):
17
+ #
18
+ # This should be handled properly.
19
+ handler.handle(request)
20
+ head :ok
21
+ rescue => e
22
+ # TODO: add exception handler here
23
+ # Appsignal.add_exception(e)
24
+
25
+ if handler&.expose_errors_to_sender?
26
+ error_for_sender_from_exception(e)
27
+ else
28
+ head :ok
29
+ end
30
+ end
31
+
32
+ def error_for_sender_from_exception(e)
33
+ case e
34
+ when HandlerRefused
35
+ render_error("Webhook handler did not validate the request (signature or authentication may be invalid)", :forbidden)
36
+ when JSON::ParserError, KeyError
37
+ render_error("Required parameters were not present in the request or the request body was not valid JSON", :bad_request)
38
+ else
39
+ render_error("Internal error", :internal_server_error)
40
+ end
41
+ end
42
+
43
+ def render_error(message_str, status_sym)
44
+ json = {error: message_str}.to_json
45
+ render(json:, status: status_sym)
46
+ end
47
+
48
+ def lookup_handler(service_id_str)
49
+ Munster.configuration.active_handlers.index_by(&:service_id).fetch(service_id_str.to_sym)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../munster"
4
+ require_relative "controllers/receive_webhooks_controller"
5
+ require_relative "jobs/processing_job"
6
+ require_relative "models/received_webhook"
7
+ require_relative "base_handler"
8
+
9
+ module Munster
10
+ class Engine < ::Rails::Engine
11
+ isolate_namespace Munster
12
+
13
+ autoload :Munster, "munster"
14
+ autoload :ReceiveWebhooksController, "munster/controllers/receive_webhooks_controller"
15
+ autoload :ProcessingJob, "munster/jobs/processing_job"
16
+ autoload :BaseHandler, "munster/base_handler"
17
+
18
+ generators do
19
+ require_relative "install_generator"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Munster
7
+ #
8
+ # Rails generator used for setting up Munster in a Rails application.
9
+ # Run it with +bin/rails g munster:install+ in your console.
10
+ #
11
+ class InstallGenerator < Rails::Generators::Base
12
+ include ActiveRecord::Generators::Migration
13
+
14
+ source_root File.expand_path("../templates", __FILE__)
15
+
16
+ def create_migration_file
17
+ migration_template "create_munster_tables.rb.erb", File.join(db_migrate_path, "create_munster_tables.rb")
18
+ end
19
+
20
+ def copy_files
21
+ template "munster.rb", File.join("config", "initializers", "munster.rb")
22
+ end
23
+
24
+ private
25
+
26
+ def migration_version
27
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job" if defined?(Rails)
4
+
5
+ module Munster
6
+ class ProcessingJob < ActiveJob::Base
7
+ def perform(webhook)
8
+ # TODO: there should be some sort of locking or concurrency control here, but it's outside of
9
+ # Munsters scope of responsibility. Developer implementing this should decide how this should be handled.
10
+ webhook.handler.process(webhook)
11
+ # TODO: remove process attribute
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../state_machine_enum"
4
+
5
+ module Munster
6
+ class ReceivedWebhook < ActiveRecord::Base
7
+ self.implicit_order_column = "created_at"
8
+ self.table_name = "received_webhooks"
9
+
10
+ include Munster::StateMachineEnum
11
+
12
+ state_machine_enum :status do |s|
13
+ s.permit_transition(:received, :processing)
14
+ s.permit_transition(:processing, :skipped)
15
+ s.permit_transition(:processing, :processed)
16
+ s.permit_transition(:processing, :error)
17
+ end
18
+
19
+ def handler
20
+ handler_module_name.constantize
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This concern adds a method called "state_enum" useful for defining an enum using
4
+ # string values along with valid state transitions. Validations will be added for the
5
+ # state transitions and a proper enum is going to be defined. For example:
6
+ #
7
+ # state_machine_enum :state do |states|
8
+ # states.permit_transition(:created, :approved_pending_settlement)
9
+ # states.permit_transition(:approved_pending_settlement, :rejected)
10
+ # states.permit_transition(:created, :rejected)
11
+ # states.permit_transition(:approved_pending_settlement, :settled)
12
+ # end
13
+ module Munster
14
+ module StateMachineEnum
15
+ extend ActiveSupport::Concern
16
+
17
+ class StatesCollector
18
+ attr_reader :states
19
+ attr_reader :after_commit_hooks
20
+ attr_reader :common_after_commit_hooks
21
+ attr_reader :after_attribute_write_hooks
22
+ attr_reader :common_after_write_hooks
23
+
24
+ def initialize
25
+ @transitions = Set.new
26
+ @states = Set.new
27
+ @after_commit_hooks = {}
28
+ @common_after_commit_hooks = []
29
+ @after_attribute_write_hooks = {}
30
+ @common_after_write_hooks = []
31
+ end
32
+
33
+ def permit_transition(from, to)
34
+ @states << from.to_s << to.to_s
35
+ @transitions << [from.to_s, to.to_s]
36
+ end
37
+
38
+ def may_transition?(from, to)
39
+ @transitions.include?([from.to_s, to.to_s])
40
+ end
41
+
42
+ def after_inline_transition_to(target_state, &blk)
43
+ @after_attribute_write_hooks[target_state.to_s] ||= []
44
+ @after_attribute_write_hooks[target_state.to_s] << blk.to_proc
45
+ end
46
+
47
+ def after_committed_transition_to(target_state, &blk)
48
+ @after_commit_hooks[target_state.to_s] ||= []
49
+ @after_commit_hooks[target_state.to_s] << blk.to_proc
50
+ end
51
+
52
+ def after_any_committed_transition(&blk)
53
+ @common_after_commit_hooks << blk.to_proc
54
+ end
55
+
56
+ def validate(model, attribute_name)
57
+ return unless model.persisted?
58
+
59
+ was = model.attribute_was(attribute_name)
60
+ is = model[attribute_name]
61
+
62
+ unless was == is || @transitions.include?([was, is])
63
+ model.errors.add(attribute_name, "Invalid transition from #{was} to #{is}")
64
+ end
65
+ end
66
+ end
67
+
68
+ class InvalidState < StandardError
69
+ end
70
+
71
+ class_methods do
72
+ def state_machine_enum(attribute_name, **options_for_enum)
73
+ # Collect the states
74
+ collector = StatesCollector.new
75
+ yield(collector).tap do
76
+ # Define the enum using labels, with string values
77
+ enum_map = collector.states.map(&:to_sym).zip(collector.states.to_a).to_h
78
+ enum(attribute_name, enum_map, **options_for_enum)
79
+
80
+ # Define validations for transitions
81
+ validates attribute_name, presence: true
82
+ validate { |model| collector.validate(model, attribute_name) }
83
+
84
+ # Define inline hooks
85
+ before_save do |model|
86
+ _value_was, value_has_become = model.changes[attribute_name]
87
+ next unless value_has_become
88
+ hook_procs = collector.after_attribute_write_hooks[value_has_become].to_a + collector.common_after_write_hooks.to_a
89
+ hook_procs.each do |hook_proc|
90
+ hook_proc.call(model)
91
+ end
92
+ end
93
+
94
+ # Define after commit hooks
95
+ after_commit do |model|
96
+ _value_was, value_has_become = model.previous_changes[attribute_name]
97
+ next unless value_has_become
98
+ hook_procs = collector.after_commit_hooks[value_has_become].to_a + collector.common_after_commit_hooks.to_a
99
+ hook_procs.each do |hook_proc|
100
+ hook_proc.call(model)
101
+ end
102
+ end
103
+
104
+ # Define the check methods
105
+ define_method(:"ensure_#{attribute_name}_one_of!") do |*allowed_states|
106
+ val = self[attribute_name]
107
+ return if Set.new(allowed_states.map(&:to_s)).include?(val)
108
+ raise InvalidState, "#{attribute_name} must be one of #{allowed_states.inspect} but was #{val.inspect}"
109
+ end
110
+
111
+ define_method(:"ensure_#{attribute_name}_may_transition_to!") do |next_state|
112
+ val = self[attribute_name]
113
+ raise InvalidState, "#{attribute_name} already is #{val.inspect}" if next_state.to_s == val
114
+ end
115
+
116
+ define_method(:"#{attribute_name}_may_transition_to?") do |next_state|
117
+ val = self[attribute_name]
118
+ return false if val == next_state.to_s
119
+ collector.may_transition?(val, next_state)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,23 @@
1
+ class CreateMunsterTables < ActiveRecord::Migration<%= migration_version %>
2
+ <% id_type = Rails.application.config.generators.options[:active_record][:primary_key_type] rescue nil %>
3
+ def change
4
+ create_table :received_webhooks <%= ", id: :#{id_type}" if id_type %> do |t|
5
+ t.string :handler_event_id, null: false
6
+ t.string :handler_module_name, null: false
7
+ t.string :status, default: "received", null: false
8
+
9
+ # We don't assume, that we can always parse received body as JSON. Body could be invalid or partly missing,
10
+ # we can argue how we can handle that for different integrations, but we still should be able to save this data
11
+ # if it's required. Hence, we don't use :jsonb, but :binary type column here.
12
+ t.binary :body, null: false
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ # For deduplication at ingress. UNIQUE indices in Postgres are case-sensitive
18
+ # which is what we want, as these are externally-generated IDs
19
+ add_index :received_webhooks, [:handler_module_name, :handler_event_id], unique: true, name: "webhook_dedup_idx"
20
+ # For backfill processing (to know how many skipped etc. payloads we have)
21
+ add_index :received_webhooks, [:status]
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ Munster.configure do |config|
2
+ config.active_handlers = []
3
+ config.processing_job_class = Munster::ProcessingJob
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Munster
4
+ VERSION = "0.1.0"
5
+ end
data/lib/munster.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "munster/version"
4
+ require_relative "munster/engine"
5
+ require "active_support/configurable"
6
+ require_relative "munster/jobs/processing_job"
7
+
8
+ module Munster
9
+ def self.configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ def self.configure
14
+ yield configuration
15
+ end
16
+ end
17
+
18
+ class Munster::Configuration
19
+ include ActiveSupport::Configurable
20
+
21
+ config_accessor(:processing_job_class) { Munster::ProcessingJob }
22
+ config_accessor(:active_handlers) { [] }
23
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :munster do
3
+ # # Task goes here
4
+ # end
data/sig/munster.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Munster
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end