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