webhook_system 0.1.1 → 1.0.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/CHANGELOG.md +5 -0
- data/README.md +103 -4
- data/lib/webhook_system.rb +1 -0
- data/lib/webhook_system/base_event.rb +12 -2
- data/lib/webhook_system/encoder.rb +1 -1
- data/lib/webhook_system/event_log.rb +39 -0
- data/lib/webhook_system/job.rb +34 -6
- data/lib/webhook_system/subscription.rb +2 -0
- data/lib/webhook_system/version.rb +1 -2
- data/webhook_system.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7949660ffcd7499c9ad2cd90030a700c984ee81f
|
4
|
+
data.tar.gz: db0e7b0233716c187c67cd1ec09560eb053ee30b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 433ab1c14d97195cb9222247266e5feb06e17e26d36bef61ebb313057cade5a8994600bf2730799e0f25e08c3fc026c345c8223a441a4b6383655919b2a25bb0
|
7
|
+
data.tar.gz: 2142d2b4256bc7abe4a2a3d3f408b5163ad783b61aafdf851485050c82b6ddb7f4bb38a8f438153fb283f5979ed709750abdf8d11776321fdd2289c1644027f7
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v0.1.1](https://github.com/payrollhero/webhook_system/tree/v0.1.1) (2016-02-05)
|
4
|
+
[Full Changelog](https://github.com/payrollhero/webhook_system/compare/v0.1.0...v0.1.1)
|
5
|
+
|
6
|
+
- Fixing hash based attribute definition and a few fixes [\#3](https://github.com/payrollhero/webhook_system/pull/3) ([piotrb](https://github.com/piotrb))
|
7
|
+
|
3
8
|
## [v0.1.0](https://github.com/payrollhero/webhook_system/tree/v0.1.0) (2016-02-05)
|
4
9
|
[Full Changelog](https://github.com/payrollhero/webhook_system/compare/v0.0.1...v0.1.0)
|
5
10
|
|
data/README.md
CHANGED
@@ -28,20 +28,119 @@ tables first:
|
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
create_table :webhook_subscriptions do |t|
|
31
|
-
t.string :url
|
32
|
-
t.boolean :active
|
31
|
+
t.string :url, null: false
|
32
|
+
t.boolean :active, null: false
|
33
33
|
t.text :secret
|
34
34
|
|
35
35
|
t.index :active
|
36
36
|
end
|
37
37
|
|
38
38
|
create_table :webhook_subscription_topics do |t|
|
39
|
-
t.string :name
|
40
|
-
t.belongs_to :subscription
|
39
|
+
t.string :name, null: false
|
40
|
+
t.belongs_to :subscription, null: false
|
41
41
|
|
42
42
|
t.index :subscription_id
|
43
43
|
t.index :name
|
44
44
|
end
|
45
|
+
|
46
|
+
create_table :webhook_event_logs do |t|
|
47
|
+
t.belongs_to :subscription, null: false
|
48
|
+
|
49
|
+
t.string :event_name, null: false
|
50
|
+
t.string :event_id, null: false
|
51
|
+
t.integer :status, null: false
|
52
|
+
|
53
|
+
t.text :request, limit: 64_000, null: false
|
54
|
+
t.text :response, limit: 64_000, null: false
|
55
|
+
|
56
|
+
t.datetime :created_at, null: false
|
57
|
+
|
58
|
+
t.index :created_at
|
59
|
+
t.index :event_name
|
60
|
+
t.index :status
|
61
|
+
t.index :subscription_id
|
62
|
+
t.index :event_id
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### Migrating from version 0.x
|
67
|
+
|
68
|
+
First migrate the null constraints in ...
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
def up
|
72
|
+
change_column :webhook_subscriptions, :url, null: false
|
73
|
+
change_column :webhook_subscriptions, :active, null: false
|
74
|
+
change_column :webhook_subscription_topics, :name, null: false
|
75
|
+
change_column :webhook_subscription_topics, :subscription_id, null: false
|
76
|
+
end
|
77
|
+
|
78
|
+
def down
|
79
|
+
change_column :webhook_subscription_topics, :subscription_id, null: true
|
80
|
+
change_column :webhook_subscription_topics, :name, null: true
|
81
|
+
change_column :webhook_subscriptions, :active, null: true
|
82
|
+
change_column :webhook_subscriptions, :url, null: true
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Then add the new table ...
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
create_table :webhook_event_logs do |t|
|
90
|
+
t.belongs_to :subscription, null: false
|
91
|
+
|
92
|
+
t.string :event_name, null: false
|
93
|
+
t.string :event_id, null: false
|
94
|
+
t.integer :status, null: false
|
95
|
+
|
96
|
+
t.text :request, limit: 64_000, null: false
|
97
|
+
t.text :response, limit: 64_000, null: false
|
98
|
+
|
99
|
+
t.datetime :created_at, null: false
|
100
|
+
|
101
|
+
t.index :created_at
|
102
|
+
t.index :event_name
|
103
|
+
t.index :status
|
104
|
+
t.index :subscription_id
|
105
|
+
t.index :event_id
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
## Configuring the ActiveJob Job
|
110
|
+
|
111
|
+
There is a couple of things you might need to configure in your handling of these jobs
|
112
|
+
|
113
|
+
### Queue Name
|
114
|
+
|
115
|
+
You might need to reopen the class and define the queue:
|
116
|
+
|
117
|
+
eg:
|
118
|
+
```ruby
|
119
|
+
class WebhookSystem::Job
|
120
|
+
queue_as :some_queue_name
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Error Handling
|
125
|
+
|
126
|
+
By default the job will fail itself when it gets a non 200 response. You can handle these errors by rescuing the
|
127
|
+
Request failed exception. eg:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class WebhookSystem::Job
|
131
|
+
rescue_from(WebhookSystem::Job::RequestFailed) do |exception|
|
132
|
+
case exception.code
|
133
|
+
when 200..299
|
134
|
+
# this is ok, ignore it
|
135
|
+
when 300..399
|
136
|
+
# this is kinda ok, but let's log it ..
|
137
|
+
Rails.logger.info "weird response"
|
138
|
+
else
|
139
|
+
# otherwise re-raise it so the job fails
|
140
|
+
raise exception
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
45
144
|
```
|
46
145
|
|
47
146
|
## Building Events
|
data/lib/webhook_system.rb
CHANGED
@@ -4,6 +4,13 @@ module WebhookSystem
|
|
4
4
|
class BaseEvent
|
5
5
|
include PhModel
|
6
6
|
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
@event_id = SecureRandom.uuid.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :event_id
|
13
|
+
|
7
14
|
def event_name
|
8
15
|
mesg = "class #{self.class.name} must implement abstract method `#{self.class.name}#event_name()'."
|
9
16
|
raise RuntimeError.new(mesg).tap { |err| err.backtrace = caller }
|
@@ -15,7 +22,10 @@ module WebhookSystem
|
|
15
22
|
end
|
16
23
|
|
17
24
|
def as_json
|
18
|
-
result = {
|
25
|
+
result = {
|
26
|
+
'event_name' => event_name,
|
27
|
+
'event_id' => event_id,
|
28
|
+
}
|
19
29
|
each_attribute do |attribute_name, attribute_method|
|
20
30
|
validate_attribute_name attribute_name
|
21
31
|
result[attribute_name.to_s] = public_send(attribute_method).as_json
|
@@ -24,7 +34,7 @@ module WebhookSystem
|
|
24
34
|
end
|
25
35
|
|
26
36
|
def self.key_is_reserved?(key)
|
27
|
-
key.to_s.in? %w(event)
|
37
|
+
key.to_s.in? %w(event event_id)
|
28
38
|
end
|
29
39
|
|
30
40
|
private
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module WebhookSystem
|
2
|
+
|
3
|
+
# This is the model holding on to all webhook responses
|
4
|
+
class EventLog < ActiveRecord::Base
|
5
|
+
self.table_name = 'webhook_event_logs'
|
6
|
+
|
7
|
+
belongs_to :subscription, class_name: 'WebhookSystem::Subscription'
|
8
|
+
|
9
|
+
validates :event_id, presence: true
|
10
|
+
validates :subscription_id, presence: true
|
11
|
+
validates :event_name, presence: true
|
12
|
+
validates :status, presence: true
|
13
|
+
|
14
|
+
serialize :request, JSON
|
15
|
+
serialize :response, JSON
|
16
|
+
|
17
|
+
def self.construct(subscription, event, request, response)
|
18
|
+
request_info = {
|
19
|
+
'event' => event,
|
20
|
+
'headers' => request.headers.to_hash,
|
21
|
+
'body' => request.body,
|
22
|
+
'url' => request.path,
|
23
|
+
}
|
24
|
+
response_info = {
|
25
|
+
'headers' => response.headers.to_hash,
|
26
|
+
'body' => response.body,
|
27
|
+
}
|
28
|
+
|
29
|
+
attributes = {
|
30
|
+
event_name: event['event_name'],
|
31
|
+
event_id: event['event_id'],
|
32
|
+
status: response.status,
|
33
|
+
request: request_info,
|
34
|
+
response: response_info,
|
35
|
+
}
|
36
|
+
subscription.event_logs.build(attributes)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/webhook_system/job.rb
CHANGED
@@ -3,20 +3,48 @@ module WebhookSystem
|
|
3
3
|
# This is the ActiveJob in charge of actually sending each event
|
4
4
|
class Job < ActiveJob::Base
|
5
5
|
|
6
|
+
# Exception class around non 200 responses
|
7
|
+
class RequestFailed < RuntimeError
|
8
|
+
def initialize(message, code)
|
9
|
+
super(message)
|
10
|
+
@code = code
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
def perform(subscription, event)
|
7
|
-
|
8
|
-
|
15
|
+
self.class.post(subscription, event)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.post(subscription, event)
|
19
|
+
client = build_client
|
20
|
+
request = build_request(client, subscription, event)
|
21
|
+
response = client.builder.build_response(client, request)
|
22
|
+
log_response(subscription, event, request, response)
|
23
|
+
ensure_success(response)
|
9
24
|
end
|
10
25
|
|
11
|
-
def self.
|
12
|
-
|
26
|
+
def self.ensure_success(response)
|
27
|
+
status = response.status
|
28
|
+
unless status == 200
|
29
|
+
raise RequestFailed.new("request failed with code: #{status}", status)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.build_request(client, subscription, event)
|
34
|
+
payload = Encoder.encode(subscription.secret, event)
|
35
|
+
client.build_request(:post) do |req|
|
36
|
+
req.url subscription.url
|
13
37
|
req.headers['Content-Type'] = 'application/json; base64+aes256'
|
14
38
|
req.body = payload.to_s
|
15
39
|
end
|
16
40
|
end
|
17
41
|
|
18
|
-
def self.
|
19
|
-
|
42
|
+
def self.log_response(subscription, event, request, response)
|
43
|
+
EventLog.construct(subscription, event, request, response).save!
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.build_client
|
47
|
+
Faraday.new do |faraday|
|
20
48
|
faraday.response :logger if ENV['WEBHOOK_DEBUG']
|
21
49
|
faraday.adapter Faraday.default_adapter
|
22
50
|
end
|
@@ -8,6 +8,8 @@ module WebhookSystem
|
|
8
8
|
validates :secret, presence: true
|
9
9
|
|
10
10
|
has_many :topics, class_name: 'WebhookSystem::SubscriptionTopic', dependent: :destroy
|
11
|
+
has_many :event_logs, class_name: 'WebhookSystem::EventLog', dependent: :destroy
|
12
|
+
|
11
13
|
accepts_nested_attributes_for :topics, allow_destroy: true
|
12
14
|
|
13
15
|
scope :active, -> { where(active: true) }
|
data/webhook_system.gemspec
CHANGED
@@ -36,6 +36,6 @@ Gem::Specification.new do |gem|
|
|
36
36
|
gem.add_development_dependency 'webmock'
|
37
37
|
|
38
38
|
# static analysis gems
|
39
|
-
gem.add_development_dependency 'rubocop', '~> 0.
|
39
|
+
gem.add_development_dependency 'rubocop', '~> 0.37.1'
|
40
40
|
gem.add_development_dependency 'reek', '~> 3.7'
|
41
41
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webhook_system
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Banasik
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -226,14 +226,14 @@ dependencies:
|
|
226
226
|
requirements:
|
227
227
|
- - "~>"
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
version: 0.
|
229
|
+
version: 0.37.1
|
230
230
|
type: :development
|
231
231
|
prerelease: false
|
232
232
|
version_requirements: !ruby/object:Gem::Requirement
|
233
233
|
requirements:
|
234
234
|
- - "~>"
|
235
235
|
- !ruby/object:Gem::Version
|
236
|
-
version: 0.
|
236
|
+
version: 0.37.1
|
237
237
|
- !ruby/object:Gem::Dependency
|
238
238
|
name: reek
|
239
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -271,6 +271,7 @@ files:
|
|
271
271
|
- lib/webhook_system/base_event.rb
|
272
272
|
- lib/webhook_system/dispatcher.rb
|
273
273
|
- lib/webhook_system/encoder.rb
|
274
|
+
- lib/webhook_system/event_log.rb
|
274
275
|
- lib/webhook_system/job.rb
|
275
276
|
- lib/webhook_system/subscription.rb
|
276
277
|
- lib/webhook_system/subscription_topic.rb
|