hooksmith 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +46 -0
- data/lib/hooksmith/config/event_store.rb +47 -0
- data/lib/hooksmith/config/provider.rb +26 -0
- data/lib/hooksmith/configuration.rb +20 -20
- data/lib/hooksmith/dispatcher.rb +9 -1
- data/lib/hooksmith/event_recorder.rb +54 -0
- data/lib/hooksmith/version.rb +1 -1
- data/lib/hooksmith.rb +3 -0
- metadata +5 -3
- data/hooksmith-0.1.0.gem +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82cacda1d785736b14476d699c2d18e90d65036f2b4e291d03dd2b393a907d14
|
4
|
+
data.tar.gz: 65407cf717e2e3ac6edede0392ed319250b3e0c06fc61dc0d730fd82fafead3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbd5f49b4248a597997816a03c0e9624c93ec3fe8185837398564360a16663defe758463ba2528f1af38e53e0816fadf1b61d5243591835f8b84cd9c822a7f40
|
7
|
+
data.tar.gz: b13ef16ba8d73c011c410ca838575f9a4eb8b3bdc58560b392cf88350f8520d0752a7081e556dc649fa5e6ad89441e975bde9e57085717b98356516a6e801f81
|
data/README.md
CHANGED
@@ -48,6 +48,52 @@ Hooksmith.configure do |config|
|
|
48
48
|
end
|
49
49
|
```
|
50
50
|
|
51
|
+
### Persisting Webhook Events (Optional)
|
52
|
+
|
53
|
+
Hooksmith can optionally persist incoming webhook events to your database. Configure it with your own ActiveRecord model and mapping logic.
|
54
|
+
|
55
|
+
1) Create a model in your app (example):
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class WebhookEvent < ApplicationRecord
|
59
|
+
self.table_name = 'webhook_events'
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
2) Example migration (customize fields as needed):
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
create_table :webhook_events do |t|
|
67
|
+
t.string :provider
|
68
|
+
t.string :event
|
69
|
+
t.jsonb :payload
|
70
|
+
t.datetime :received_at
|
71
|
+
t.timestamps
|
72
|
+
t.index :event
|
73
|
+
t.index :received_at
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
3) Configure Hooksmith to record events:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Hooksmith.configure do |config|
|
81
|
+
config.event_store do |store|
|
82
|
+
store.enabled = true
|
83
|
+
store.model_class_name = 'WebhookEvent' # your model class
|
84
|
+
store.record_timing = :before # :before, :after, or :both
|
85
|
+
store.mapper = ->(provider:, event:, payload:) {
|
86
|
+
{
|
87
|
+
provider: provider.to_s,
|
88
|
+
event: event.to_s,
|
89
|
+
payload:,
|
90
|
+
received_at: (Time.respond_to?(:current) ? Time.current : Time.now)
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
51
97
|
## Implementing a Processor
|
52
98
|
Create a processor by inheriting from `Hooksmith::Processor::Base`:
|
53
99
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hooksmith
|
4
|
+
module Config
|
5
|
+
# EventStore holds settings for optional event persistence.
|
6
|
+
class EventStore
|
7
|
+
# Whether persistence is enabled.
|
8
|
+
attr_accessor :enabled
|
9
|
+
# Class name of the model used to persist events. Must respond to .create!(attrs)
|
10
|
+
attr_accessor :model_class_name
|
11
|
+
# Proc to map provider/event/payload to attributes persisted
|
12
|
+
attr_accessor :mapper
|
13
|
+
# When to record: :before, :after, or :both
|
14
|
+
attr_accessor :record_timing
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@enabled = false
|
18
|
+
# No default model in the gem; applications should provide their own model
|
19
|
+
@model_class_name = nil
|
20
|
+
@record_timing = :before
|
21
|
+
@mapper = default_mapper
|
22
|
+
end
|
23
|
+
|
24
|
+
def model_class
|
25
|
+
return nil if model_class_name.nil?
|
26
|
+
|
27
|
+
Object.const_get(model_class_name)
|
28
|
+
rescue NameError
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def default_mapper
|
35
|
+
lambda do |provider:, event:, payload:|
|
36
|
+
now = Time.respond_to?(:current) ? Time.current : Time.now
|
37
|
+
{
|
38
|
+
provider: provider.to_s,
|
39
|
+
event: event.to_s,
|
40
|
+
payload:,
|
41
|
+
received_at: now
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hooksmith
|
4
|
+
module Config
|
5
|
+
# Provider is used internally by the DSL to collect processor registrations.
|
6
|
+
class Provider
|
7
|
+
# @return [Symbol, String] the provider name.
|
8
|
+
attr_reader :provider
|
9
|
+
# @return [Array<Hash>] list of entries registered.
|
10
|
+
attr_reader :entries
|
11
|
+
|
12
|
+
def initialize(provider)
|
13
|
+
@provider = provider
|
14
|
+
@entries = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Registers a processor for a specific event.
|
18
|
+
#
|
19
|
+
# @param event [Symbol, String] the event name.
|
20
|
+
# @param processor_class_name [String] the processor class name.
|
21
|
+
def register(event, processor_class_name)
|
22
|
+
entries << { event: event.to_sym, processor: processor_class_name }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -7,18 +7,21 @@ module Hooksmith
|
|
7
7
|
class Configuration
|
8
8
|
# @return [Hash] a registry mapping provider symbols to arrays of processor entries.
|
9
9
|
attr_reader :registry
|
10
|
+
# @return [Hooksmith::Config::EventStore] configuration for event persistence
|
11
|
+
attr_reader :event_store_config
|
10
12
|
|
11
13
|
def initialize
|
12
14
|
# Registry structure: { provider_symbol => [{ event: event_symbol, processor: ProcessorClass }, ...] }
|
13
15
|
@registry = Hash.new { |hash, key| hash[key] = [] }
|
16
|
+
@event_store_config = Hooksmith::Config::EventStore.new
|
14
17
|
end
|
15
18
|
|
16
19
|
# Groups registrations under a specific provider.
|
17
20
|
#
|
18
21
|
# @param provider_name [Symbol, String] the provider name (e.g., :stripe)
|
19
|
-
# @yield [
|
22
|
+
# @yield [Hooksmith::Config::Provider] a block yielding a Provider object
|
20
23
|
def provider(provider_name)
|
21
|
-
provider_config =
|
24
|
+
provider_config = Hooksmith::Config::Provider.new(provider_name)
|
22
25
|
yield(provider_config)
|
23
26
|
registry[provider_name.to_sym].concat(provider_config.entries)
|
24
27
|
end
|
@@ -40,26 +43,23 @@ module Hooksmith
|
|
40
43
|
def processors_for(provider, event)
|
41
44
|
registry[provider.to_sym].select { |entry| entry[:event] == event.to_sym }
|
42
45
|
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# ProviderConfig is used internally by the DSL to collect processor registrations.
|
46
|
-
class ProviderConfig
|
47
|
-
# @return [Symbol, String] the provider name.
|
48
|
-
attr_reader :provider
|
49
|
-
# @return [Array<Hash>] list of entries registered.
|
50
|
-
attr_reader :entries
|
51
|
-
|
52
|
-
def initialize(provider)
|
53
|
-
@provider = provider
|
54
|
-
@entries = []
|
55
|
-
end
|
56
46
|
|
57
|
-
#
|
47
|
+
# Configure event store persistence.
|
58
48
|
#
|
59
|
-
# @
|
60
|
-
# @
|
61
|
-
|
62
|
-
|
49
|
+
# @yield [Hooksmith::Config::EventStore] a block yielding an EventStore object
|
50
|
+
# @example
|
51
|
+
# Hooksmith.configure do |config|
|
52
|
+
# config.event_store do |store|
|
53
|
+
# store.enabled = true
|
54
|
+
# store.model_class_name = 'MyApp::WebhookEvent'
|
55
|
+
# store.record_timing = :before # or :after, or :both
|
56
|
+
# store.mapper = ->(provider:, event:, payload:) {
|
57
|
+
# { provider:, event: event.to_s, payload:, received_at: (Time.respond_to?(:current) ? Time.current : Time.now) }
|
58
|
+
# }
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
def event_store
|
62
|
+
yield(@event_store_config)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
data/lib/hooksmith/dispatcher.rb
CHANGED
@@ -28,6 +28,9 @@ module Hooksmith
|
|
28
28
|
#
|
29
29
|
# @raise [MultipleProcessorsError] if multiple processors qualify.
|
30
30
|
def run!
|
31
|
+
# Optionally record the incoming event before processing.
|
32
|
+
Hooksmith::EventRecorder.record!(provider: @provider, event: @event, payload: @payload, timing: :before)
|
33
|
+
|
31
34
|
# Fetch all processors registered for this provider and event.
|
32
35
|
entries = Hooksmith.configuration.processors_for(@provider, @event)
|
33
36
|
|
@@ -46,7 +49,12 @@ module Hooksmith
|
|
46
49
|
raise MultipleProcessorsError.new(@provider, @event, @payload) if matching_processors.size > 1
|
47
50
|
|
48
51
|
# Exactly one matching processor.
|
49
|
-
matching_processors.first.process!
|
52
|
+
result = matching_processors.first.process!
|
53
|
+
|
54
|
+
# Optionally record the event after successful processing.
|
55
|
+
Hooksmith::EventRecorder.record!(provider: @provider, event: @event, payload: @payload, timing: :after)
|
56
|
+
|
57
|
+
result
|
50
58
|
rescue StandardError => e
|
51
59
|
Hooksmith.logger.error("Error processing #{@provider} event #{@event}: #{e.message}")
|
52
60
|
raise e
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hooksmith
|
4
|
+
# Records webhook events to a configurable persistence model.
|
5
|
+
#
|
6
|
+
# This recorder is resilient: failures to persist are logged and do not
|
7
|
+
# impact the main processing flow.
|
8
|
+
module EventRecorder
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Record an event if the event store is enabled.
|
12
|
+
#
|
13
|
+
# @param provider [Symbol, String]
|
14
|
+
# @param event [Symbol, String]
|
15
|
+
# @param payload [Hash]
|
16
|
+
# @param timing [Symbol] one of :before or :after
|
17
|
+
def record!(provider:, event:, payload:, timing: :before)
|
18
|
+
config = Hooksmith.configuration.event_store_config
|
19
|
+
return unless config.enabled
|
20
|
+
return unless record_for_timing?(config, timing)
|
21
|
+
|
22
|
+
model_class = config.model_class
|
23
|
+
unless model_class
|
24
|
+
Hooksmith.logger.warn("Event store enabled but model '#{config.model_class_name}' not found")
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
attributes = safe_map(config, provider:, event:, payload:)
|
29
|
+
model_class.create!(attributes)
|
30
|
+
rescue StandardError => e
|
31
|
+
Hooksmith.logger.error("Failed to record webhook event: #{e.message}")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Determine whether to record depending on the configured timing.
|
35
|
+
def record_for_timing?(config, timing)
|
36
|
+
case config.record_timing
|
37
|
+
when :both then true
|
38
|
+
when :before then timing == :before
|
39
|
+
when :after then timing == :after
|
40
|
+
end
|
41
|
+
end
|
42
|
+
private_class_method :record_for_timing?
|
43
|
+
|
44
|
+
# Safely map attributes using the configured mapper.
|
45
|
+
def safe_map(config, provider:, event:, payload:)
|
46
|
+
mapper = config.mapper
|
47
|
+
mapper.call(provider:, event:, payload:)
|
48
|
+
rescue StandardError => e
|
49
|
+
Hooksmith.logger.error("Event mapper raised: #{e.message}")
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
private_class_method :safe_map
|
53
|
+
end
|
54
|
+
end
|
data/lib/hooksmith/version.rb
CHANGED
data/lib/hooksmith.rb
CHANGED
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'hooksmith/version'
|
4
4
|
require 'hooksmith/configuration'
|
5
|
+
require 'hooksmith/config/provider'
|
6
|
+
require 'hooksmith/config/event_store'
|
5
7
|
require 'hooksmith/dispatcher'
|
6
8
|
require 'hooksmith/logger'
|
9
|
+
require 'hooksmith/event_recorder'
|
7
10
|
require 'hooksmith/processor/base'
|
8
11
|
require 'hooksmith/railtie' if defined?(Rails)
|
9
12
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hooksmith
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gregoryrivage
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-05 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Hooksmith is a gem that allows you to handle webhooks in your Rails application.
|
13
13
|
It provides a simple and flexible way to receive, validate, and process webhooks
|
@@ -27,10 +27,12 @@ files:
|
|
27
27
|
- LICENSE.txt
|
28
28
|
- README.md
|
29
29
|
- Rakefile
|
30
|
-
- hooksmith-0.1.0.gem
|
31
30
|
- lib/hooksmith.rb
|
31
|
+
- lib/hooksmith/config/event_store.rb
|
32
|
+
- lib/hooksmith/config/provider.rb
|
32
33
|
- lib/hooksmith/configuration.rb
|
33
34
|
- lib/hooksmith/dispatcher.rb
|
35
|
+
- lib/hooksmith/event_recorder.rb
|
34
36
|
- lib/hooksmith/logger.rb
|
35
37
|
- lib/hooksmith/processor/base.rb
|
36
38
|
- lib/hooksmith/railtie.rb
|
data/hooksmith-0.1.0.gem
DELETED
Binary file
|