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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 845cc8863c319a2a78ff235cbb41256a9bdbe850
4
- data.tar.gz: afa19be64345c961c0989c09d09fc80341200ab4
3
+ metadata.gz: 7949660ffcd7499c9ad2cd90030a700c984ee81f
4
+ data.tar.gz: db0e7b0233716c187c67cd1ec09560eb053ee30b
5
5
  SHA512:
6
- metadata.gz: 0285d5a6e08624c92cdddf4eceed759c55f2839c139d922c09c8d82302114b0dcfba8416dae5ea5cb965ca8bdcc971c323087397ddae26b309217c9c1fb11820
7
- data.tar.gz: de888a90628e3987dc5f4b0416d7eef83e8afc6f1972135c6545ecd8839f4cacb570ef4f1d73900cadc3cd808a10329c6b43c0adb60bd805278942d858b0e3a6
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
@@ -13,6 +13,7 @@ module WebhookSystem
13
13
  autoload :Job
14
14
  autoload :Encoder
15
15
  autoload :BaseEvent
16
+ autoload :EventLog
16
17
 
17
18
  # Error raised when there is an issue with decoding the payload
18
19
  class DecodingError < RuntimeError
@@ -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 = { 'event' => event_name }
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
@@ -49,7 +49,7 @@ module WebhookSystem
49
49
  # :nodoc:
50
50
  module Payload
51
51
  def self.encode(raw_encrypted_data, iv)
52
- JSON.dump(
52
+ JSON.pretty_generate(
53
53
  'format' => 'base64+aes256',
54
54
  'payload' => Base64.encode64(raw_encrypted_data),
55
55
  'iv' => Base64.encode64(iv)
@@ -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
@@ -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
- payload = Encoder.encode(subscription.secret, event)
8
- self.class.post(subscription.url, payload)
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.post(endpoint, payload)
12
- client_for(endpoint).post do |req|
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.client_for(endpoint)
19
- Faraday.new(url: endpoint) do |faraday|
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) }
@@ -1,4 +1,3 @@
1
- # frozen_string_literal: true
2
1
  module WebhookSystem
3
- VERSION = '0.1.1'.freeze
2
+ VERSION = '1.0.0'.freeze
4
3
  end
@@ -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.36.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.1.1
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-05 00:00:00.000000000 Z
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.36.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.36.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