webhook_system 0.0.1
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 +7 -0
- data/.codeclimate.yml +9 -0
- data/.gitignore +5 -0
- data/.hound.yml +2 -0
- data/.reek +10 -0
- data/.rubocop.hound.yml +243 -0
- data/.rubocop.yml +57 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +0 -0
- data/DEPLOYING.md +25 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +12 -0
- data/README.md +155 -0
- data/Rakefile +46 -0
- data/lib/webhook_system.rb +23 -0
- data/lib/webhook_system/base_event.rb +40 -0
- data/lib/webhook_system/dispatcher.rb +16 -0
- data/lib/webhook_system/encoder.rb +69 -0
- data/lib/webhook_system/job.rb +39 -0
- data/lib/webhook_system/subscription.rb +17 -0
- data/lib/webhook_system/subscription_topic.rb +9 -0
- data/lib/webhook_system/version.rb +4 -0
- data/webhook_system.gemspec +40 -0
- metadata +289 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 05503cb6981ef8a26c904b045fa7ad24b420f6f2
|
|
4
|
+
data.tar.gz: c870e8c64ffcc3dd09785b4e856e50ee385d1f3d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7f9fa83a0a0cdc22efac7c092807c86fc45e6df6bba6fa61d7b632dc33e146c90d58f7de1860abf15b9c92d0fd0b2683892b99b90b6c3fa690c750fa041770f2
|
|
7
|
+
data.tar.gz: 309d84dd3b30e2ebe343475ce7ac02fd316b617a86f0e2ba78eff1d2e8a15704b825a7ebf272089b8bb197732a4bd95bea148ec9a9f74300a7c50bbb04203d5a
|
data/.codeclimate.yml
ADDED
data/.hound.yml
ADDED
data/.reek
ADDED
data/.rubocop.hound.yml
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
Exclude:
|
|
3
|
+
- "vendor/**/*"
|
|
4
|
+
- "db/schema.rb"
|
|
5
|
+
UseCache: false
|
|
6
|
+
Style/CollectionMethods:
|
|
7
|
+
Description: Preferred collection methods.
|
|
8
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size
|
|
9
|
+
Enabled: true
|
|
10
|
+
PreferredMethods:
|
|
11
|
+
collect: map
|
|
12
|
+
collect!: map!
|
|
13
|
+
find: detect
|
|
14
|
+
find_all: select
|
|
15
|
+
reduce: inject
|
|
16
|
+
Style/DotPosition:
|
|
17
|
+
Description: Checks the position of the dot in multi-line method calls.
|
|
18
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
|
|
19
|
+
Enabled: true
|
|
20
|
+
EnforcedStyle: trailing
|
|
21
|
+
SupportedStyles:
|
|
22
|
+
- leading
|
|
23
|
+
- trailing
|
|
24
|
+
Style/FileName:
|
|
25
|
+
Description: Use snake_case for source file names.
|
|
26
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
|
|
27
|
+
Enabled: false
|
|
28
|
+
Exclude: []
|
|
29
|
+
Style/GuardClause:
|
|
30
|
+
Description: Check for conditionals that can be replaced with guard clauses
|
|
31
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
|
|
32
|
+
Enabled: false
|
|
33
|
+
MinBodyLength: 1
|
|
34
|
+
Style/IfUnlessModifier:
|
|
35
|
+
Description: Favor modifier if/unless usage when you have a single-line body.
|
|
36
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
|
|
37
|
+
Enabled: false
|
|
38
|
+
MaxLineLength: 80
|
|
39
|
+
Style/OptionHash:
|
|
40
|
+
Description: Don't use option hashes when you can use keyword arguments.
|
|
41
|
+
Enabled: false
|
|
42
|
+
Style/PercentLiteralDelimiters:
|
|
43
|
+
Description: Use `%`-literal delimiters consistently
|
|
44
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
|
|
45
|
+
Enabled: false
|
|
46
|
+
PreferredDelimiters:
|
|
47
|
+
"%": "()"
|
|
48
|
+
"%i": "()"
|
|
49
|
+
"%q": "()"
|
|
50
|
+
"%Q": "()"
|
|
51
|
+
"%r": "{}"
|
|
52
|
+
"%s": "()"
|
|
53
|
+
"%w": "()"
|
|
54
|
+
"%W": "()"
|
|
55
|
+
"%x": "()"
|
|
56
|
+
Style/PredicateName:
|
|
57
|
+
Description: Check the names of predicate methods.
|
|
58
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
|
|
59
|
+
Enabled: true
|
|
60
|
+
NamePrefix:
|
|
61
|
+
- is_
|
|
62
|
+
- has_
|
|
63
|
+
- have_
|
|
64
|
+
NamePrefixBlacklist:
|
|
65
|
+
- is_
|
|
66
|
+
Exclude:
|
|
67
|
+
- spec/**/*
|
|
68
|
+
Style/RaiseArgs:
|
|
69
|
+
Description: Checks the arguments passed to raise/fail.
|
|
70
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
|
|
71
|
+
Enabled: false
|
|
72
|
+
EnforcedStyle: exploded
|
|
73
|
+
SupportedStyles:
|
|
74
|
+
- compact
|
|
75
|
+
- exploded
|
|
76
|
+
Style/SignalException:
|
|
77
|
+
Description: Checks for proper usage of fail and raise.
|
|
78
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
|
|
79
|
+
Enabled: false
|
|
80
|
+
EnforcedStyle: semantic
|
|
81
|
+
SupportedStyles:
|
|
82
|
+
- only_raise
|
|
83
|
+
- only_fail
|
|
84
|
+
- semantic
|
|
85
|
+
Style/SingleLineBlockParams:
|
|
86
|
+
Description: Enforces the names of some block params.
|
|
87
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
|
|
88
|
+
Enabled: false
|
|
89
|
+
Methods:
|
|
90
|
+
- reduce:
|
|
91
|
+
- a
|
|
92
|
+
- e
|
|
93
|
+
- inject:
|
|
94
|
+
- a
|
|
95
|
+
- e
|
|
96
|
+
Style/SingleLineMethods:
|
|
97
|
+
Description: Avoid single-line methods.
|
|
98
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
|
|
99
|
+
Enabled: false
|
|
100
|
+
AllowIfMethodIsEmpty: true
|
|
101
|
+
Style/StringLiterals:
|
|
102
|
+
Description: Checks if uses of quotes match the configured preference.
|
|
103
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals
|
|
104
|
+
Enabled: true
|
|
105
|
+
EnforcedStyle: double_quotes
|
|
106
|
+
SupportedStyles:
|
|
107
|
+
- single_quotes
|
|
108
|
+
- double_quotes
|
|
109
|
+
Style/StringLiteralsInInterpolation:
|
|
110
|
+
Description: Checks if uses of quotes inside expressions in interpolated strings
|
|
111
|
+
match the configured preference.
|
|
112
|
+
Enabled: true
|
|
113
|
+
EnforcedStyle: single_quotes
|
|
114
|
+
SupportedStyles:
|
|
115
|
+
- single_quotes
|
|
116
|
+
- double_quotes
|
|
117
|
+
Style/TrailingCommaInArguments:
|
|
118
|
+
Description: 'Checks for trailing comma in argument lists.'
|
|
119
|
+
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
|
|
120
|
+
Enabled: false
|
|
121
|
+
EnforcedStyleForMultiline: no_comma
|
|
122
|
+
SupportedStyles:
|
|
123
|
+
- comma
|
|
124
|
+
- consistent_comma
|
|
125
|
+
- no_comma
|
|
126
|
+
Style/TrailingCommaInLiteral:
|
|
127
|
+
Description: 'Checks for trailing comma in array and hash literals.'
|
|
128
|
+
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
|
|
129
|
+
Enabled: false
|
|
130
|
+
EnforcedStyleForMultiline: no_comma
|
|
131
|
+
SupportedStyles:
|
|
132
|
+
- comma
|
|
133
|
+
- consistent_comma
|
|
134
|
+
- no_comma
|
|
135
|
+
Metrics/AbcSize:
|
|
136
|
+
Description: A calculated magnitude based on number of assignments, branches, and
|
|
137
|
+
conditions.
|
|
138
|
+
Enabled: false
|
|
139
|
+
Max: 15
|
|
140
|
+
Metrics/ClassLength:
|
|
141
|
+
Description: Avoid classes longer than 100 lines of code.
|
|
142
|
+
Enabled: false
|
|
143
|
+
CountComments: false
|
|
144
|
+
Max: 100
|
|
145
|
+
Metrics/ModuleLength:
|
|
146
|
+
CountComments: false
|
|
147
|
+
Max: 100
|
|
148
|
+
Description: Avoid modules longer than 100 lines of code.
|
|
149
|
+
Enabled: false
|
|
150
|
+
Metrics/CyclomaticComplexity:
|
|
151
|
+
Description: A complexity metric that is strongly correlated to the number of test
|
|
152
|
+
cases needed to validate a method.
|
|
153
|
+
Enabled: false
|
|
154
|
+
Max: 6
|
|
155
|
+
Metrics/MethodLength:
|
|
156
|
+
Description: Avoid methods longer than 10 lines of code.
|
|
157
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
|
|
158
|
+
Enabled: false
|
|
159
|
+
CountComments: false
|
|
160
|
+
Max: 10
|
|
161
|
+
Metrics/ParameterLists:
|
|
162
|
+
Description: Avoid parameter lists longer than three or four parameters.
|
|
163
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
|
|
164
|
+
Enabled: false
|
|
165
|
+
Max: 5
|
|
166
|
+
CountKeywordArgs: true
|
|
167
|
+
Metrics/PerceivedComplexity:
|
|
168
|
+
Description: A complexity metric geared towards measuring complexity for a human
|
|
169
|
+
reader.
|
|
170
|
+
Enabled: false
|
|
171
|
+
Max: 7
|
|
172
|
+
Lint/AssignmentInCondition:
|
|
173
|
+
Description: Don't use assignment in conditions.
|
|
174
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
|
|
175
|
+
Enabled: false
|
|
176
|
+
AllowSafeAssignment: true
|
|
177
|
+
Style/InlineComment:
|
|
178
|
+
Description: Avoid inline comments.
|
|
179
|
+
Enabled: false
|
|
180
|
+
Style/AccessorMethodName:
|
|
181
|
+
Description: Check the naming of accessor methods for get_/set_.
|
|
182
|
+
Enabled: false
|
|
183
|
+
Style/Alias:
|
|
184
|
+
Description: Use alias_method instead of alias.
|
|
185
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
|
|
186
|
+
Enabled: false
|
|
187
|
+
Style/Documentation:
|
|
188
|
+
Description: Document classes and non-namespace modules.
|
|
189
|
+
Enabled: false
|
|
190
|
+
Style/DoubleNegation:
|
|
191
|
+
Description: Checks for uses of double negation (!!).
|
|
192
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
|
|
193
|
+
Enabled: false
|
|
194
|
+
Style/EachWithObject:
|
|
195
|
+
Description: Prefer `each_with_object` over `inject` or `reduce`.
|
|
196
|
+
Enabled: false
|
|
197
|
+
Style/EmptyLiteral:
|
|
198
|
+
Description: Prefer literals to Array.new/Hash.new/String.new.
|
|
199
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
|
|
200
|
+
Enabled: false
|
|
201
|
+
Style/ModuleFunction:
|
|
202
|
+
Description: Checks for usage of `extend self` in modules.
|
|
203
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
|
|
204
|
+
Enabled: false
|
|
205
|
+
Style/OneLineConditional:
|
|
206
|
+
Description: Favor the ternary operator(?:) over if/then/else/end constructs.
|
|
207
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
|
|
208
|
+
Enabled: false
|
|
209
|
+
Style/PerlBackrefs:
|
|
210
|
+
Description: Avoid Perl-style regex back references.
|
|
211
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
|
|
212
|
+
Enabled: false
|
|
213
|
+
Style/Send:
|
|
214
|
+
Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send`
|
|
215
|
+
may overlap with existing methods.
|
|
216
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send
|
|
217
|
+
Enabled: false
|
|
218
|
+
Style/SpecialGlobalVars:
|
|
219
|
+
Description: Avoid Perl-style global variables.
|
|
220
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
|
|
221
|
+
Enabled: false
|
|
222
|
+
Style/VariableInterpolation:
|
|
223
|
+
Description: Don't interpolate global, instance and class variables directly in
|
|
224
|
+
strings.
|
|
225
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
|
|
226
|
+
Enabled: false
|
|
227
|
+
Style/WhenThen:
|
|
228
|
+
Description: Use when x then ... for one-line cases.
|
|
229
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
|
|
230
|
+
Enabled: false
|
|
231
|
+
Lint/EachWithObjectArgument:
|
|
232
|
+
Description: Check for immutable argument given to each_with_object.
|
|
233
|
+
Enabled: true
|
|
234
|
+
Lint/HandleExceptions:
|
|
235
|
+
Description: Don't suppress exception.
|
|
236
|
+
StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
|
|
237
|
+
Enabled: false
|
|
238
|
+
Lint/LiteralInCondition:
|
|
239
|
+
Description: Checks of literals used in conditions.
|
|
240
|
+
Enabled: false
|
|
241
|
+
Lint/LiteralInInterpolation:
|
|
242
|
+
Description: Checks for literals used in interpolation.
|
|
243
|
+
Enabled: false
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
inherit_from:
|
|
2
|
+
- .rubocop.hound.yml
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
Exclude:
|
|
6
|
+
- '*.gemspec'
|
|
7
|
+
- 'Gemfile'
|
|
8
|
+
|
|
9
|
+
Style/Lambda:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Style/EmptyLinesAroundClassBody:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Style/EmptyLinesAroundModuleBody:
|
|
16
|
+
Enabled: false
|
|
17
|
+
|
|
18
|
+
Style/EmptyLinesAroundMethodBody:
|
|
19
|
+
Enabled: false
|
|
20
|
+
|
|
21
|
+
Style/ClassCheck:
|
|
22
|
+
Enabled: false
|
|
23
|
+
# we don't care about kind_of? vs is_a?
|
|
24
|
+
|
|
25
|
+
Style/StringLiterals:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
Style/FileName:
|
|
29
|
+
Enabled: false
|
|
30
|
+
|
|
31
|
+
Style/RedundantException:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Style/SignalException:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
Style/BlockDelimiters:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Style/CollectionMethods:
|
|
41
|
+
PreferredMethods:
|
|
42
|
+
detect: find
|
|
43
|
+
|
|
44
|
+
# Github's PR width is 120 characters
|
|
45
|
+
Metrics/LineLength:
|
|
46
|
+
Max: 120
|
|
47
|
+
AllowURI: true
|
|
48
|
+
|
|
49
|
+
# Align with the style guide, we don't prefer anything
|
|
50
|
+
Style/CollectionMethods:
|
|
51
|
+
Enabled: false
|
|
52
|
+
|
|
53
|
+
Metrics/AbcSize:
|
|
54
|
+
Description: A calculated magnitude based on number of assignments, branches, and
|
|
55
|
+
conditions.
|
|
56
|
+
Enabled: true
|
|
57
|
+
Max: 30
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
|
File without changes
|
data/DEPLOYING.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Setup
|
|
2
|
+
|
|
3
|
+
In order to deploy a new version of the gem into the wild ...
|
|
4
|
+
|
|
5
|
+
You will need to configure your github api token for the changelog.
|
|
6
|
+
|
|
7
|
+
Generate a new token for changelogs [here](https://github.com/settings/tokens/new).
|
|
8
|
+
|
|
9
|
+
add:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
export CHANGELOG_GITHUB_TOKEN=YOUR_CHANGELOG_API_TOKEN
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
somewhere in your shell init. (ie .zshrc or simillar)
|
|
16
|
+
|
|
17
|
+
## Deploying
|
|
18
|
+
|
|
19
|
+
1. Update `lib/webhook_system/version.rb`
|
|
20
|
+
2. Commit the changed files with the version number eg: `1.8.0`
|
|
21
|
+
3. Push this to git
|
|
22
|
+
4. Run `rake release`
|
|
23
|
+
5. Run `rake changelog` (has to be ran after release since its based on github tagging)
|
|
24
|
+
6. Commit the changed changelog named something like `changelog for 1.8.0`
|
|
25
|
+
7. Push this to git
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
2
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
|
3
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
4
|
+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
|
7
|
+
the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
10
|
+
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
|
11
|
+
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
12
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# webhook_system
|
|
2
|
+
|
|
3
|
+
* [Homepage](https://rubygems.org/gems/webhook_system)
|
|
4
|
+
* [Documentation](http://rubydoc.info/gems/webhook_system/frames)
|
|
5
|
+
* [Email](mailto:piotr.banasik at gmail.com)
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
[](https://travis-ci.org/payrollhero/webhook_engine)
|
|
10
|
+
[](https://codeclimate.com/github/payrollhero/webhook_system)
|
|
11
|
+
[](https://codeclimate.com/github/payrollhero/webhook_system)
|
|
12
|
+
[](https://gemnasium.com/payrollhero/webhook_system)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Few Main points ..
|
|
17
|
+
|
|
18
|
+
1. The "server" holds on to a record of subscriptions
|
|
19
|
+
2. Each subscription has a secret attached to it
|
|
20
|
+
3. This secret is used to encrypt the entire payload of the webhook using AES-256
|
|
21
|
+
4. The webhook is delivered to the recipient as a JSON payload with a base64 encoded data component
|
|
22
|
+
5. The recipient is meant to use their copy of this secret to decode that payload, and then action it as needed.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
The webhook integration code runs on two tables. You need to create a new migration that adds these
|
|
27
|
+
tables first:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
create_table :webhook_subscriptions do |t|
|
|
31
|
+
t.string :url
|
|
32
|
+
t.boolean :active
|
|
33
|
+
t.text :secret
|
|
34
|
+
|
|
35
|
+
t.index :active
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
create_table :webhook_subscription_topics do |t|
|
|
39
|
+
t.string :name
|
|
40
|
+
t.belongs_to :subscription
|
|
41
|
+
|
|
42
|
+
t.index :subscription_id
|
|
43
|
+
t.index :name
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Building Events
|
|
48
|
+
|
|
49
|
+
Each event type should be a discrete class inheriting from `WebhookSystem::BaseEvent`.
|
|
50
|
+
|
|
51
|
+
Each class should define `event_name` and `payload_attributes` to set up the serializer properly.
|
|
52
|
+
|
|
53
|
+
### Payload Attributes
|
|
54
|
+
|
|
55
|
+
It seems worth mentioning a bit more about these. This whole system in the event is an abstraction system around
|
|
56
|
+
cleanly serializing these objects. The attribute list can take two forms:
|
|
57
|
+
|
|
58
|
+
Either as a simple array:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
def payload_attributes
|
|
62
|
+
[
|
|
63
|
+
:widget,
|
|
64
|
+
:name,
|
|
65
|
+
]
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This version just maps 1:1 the attribute to a method on the event. This means that in this example it'd expect
|
|
70
|
+
`widget` and `name` to be public methods on the event.
|
|
71
|
+
|
|
72
|
+
The alternate form is to use a Hash.
|
|
73
|
+
|
|
74
|
+
eg:
|
|
75
|
+
```ruby
|
|
76
|
+
def payload_attributes
|
|
77
|
+
{
|
|
78
|
+
widget: :get_widget,
|
|
79
|
+
name: :get_name,
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This version makes the keys the attributes in the JSON, and the values the method names to call to get the value.
|
|
85
|
+
|
|
86
|
+
### Working with Events
|
|
87
|
+
|
|
88
|
+
The general idea is that Events are ActiveModel objects using the PhModel system, so the same APIs apply.
|
|
89
|
+
|
|
90
|
+
You'd build them with `.build`. Eg:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
event_object = SomeEvent.build(name: 'John', age: 21)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
These attributes shouldn't necessarily be already the direct data hashes, let the Event do its own presentation.
|
|
97
|
+
Its perfectly OK to send these events with ActiveRecord parameters, and internally translate out of them to something
|
|
98
|
+
more suitable for the actual notification payload.
|
|
99
|
+
|
|
100
|
+
## Dispatching Events
|
|
101
|
+
|
|
102
|
+
The general API for this is via:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
WebhookSystem.dispatch(event_object)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This is meant to be fairly fire and forget. Internally this will create an ActiveJob for each subscription
|
|
109
|
+
interested in the event.
|
|
110
|
+
|
|
111
|
+
## Payload Encryption
|
|
112
|
+
|
|
113
|
+
The payload is encrypted using AES-256. Each subscription is meant to have the recipient's shared secret on it.
|
|
114
|
+
This secret is then used to encrypt the payload, so the other side needs that same secret again to open it.
|
|
115
|
+
|
|
116
|
+
The payload then will be a json post body, with the Base64 encoded payload inside it.
|
|
117
|
+
|
|
118
|
+
## Payload Decryption
|
|
119
|
+
|
|
120
|
+
There is a utility function available to decode the entire POST body of the webhook that can be used by clients.
|
|
121
|
+
|
|
122
|
+
Example use would be:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
payload = WebhookSystem::Encoder.decode(secret_string, request.body)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You will need your webhook secret, and you get back a Hash of the event's data.
|
|
129
|
+
All events are guaranteed to have a key called `event` with the event's name, everything other than that is
|
|
130
|
+
custom to each individual event type.
|
|
131
|
+
|
|
132
|
+
Some more specifics around the format for the benefit of non-ruby implementations:
|
|
133
|
+
|
|
134
|
+
The payload looks like this:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"format": "base64+aes256",
|
|
139
|
+
"payload": "{Base64 String}",
|
|
140
|
+
"iv": "{Base64 String}"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The format is just a flag incase there ever is multiple formats, currently this is the only one.
|
|
145
|
+
|
|
146
|
+
The encryption used is AES256-CBC
|
|
147
|
+
|
|
148
|
+
The AES key is a PBKDF2 (Password-Based Key Derivation Function 2) as part of PKCS#5 function on the secret using
|
|
149
|
+
SHA256 HMAC. The IV is used as the salt, 100_000 iterations, and a key length of 32 bytes (or 256 bits).
|
|
150
|
+
|
|
151
|
+
The IV is used both for initializing the cipher, and also for salting the password PBKDF2 function.
|
|
152
|
+
|
|
153
|
+
## Copyright
|
|
154
|
+
|
|
155
|
+
Copyright (c) 2015 PayrollHero
|
data/Rakefile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
|
|
3
|
+
require 'rubygems/tasks'
|
|
4
|
+
Gem::Tasks.new(release: false)
|
|
5
|
+
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
7
|
+
RSpec::Core::RakeTask.new
|
|
8
|
+
|
|
9
|
+
task :test do
|
|
10
|
+
sh "rspec"
|
|
11
|
+
sh "reek"
|
|
12
|
+
sh "rubocop"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
task default: :spec
|
|
16
|
+
|
|
17
|
+
desc "copy in PayrollHero's current style config files"
|
|
18
|
+
task :styleguide do
|
|
19
|
+
require 'faraday'
|
|
20
|
+
require 'pry'
|
|
21
|
+
base = "https://raw.githubusercontent.com/payrollhero/styleguide/master/"
|
|
22
|
+
files = %w{
|
|
23
|
+
.rubocop.hound.yml
|
|
24
|
+
.rubocop.yml
|
|
25
|
+
.reek
|
|
26
|
+
.codeclimate.yml
|
|
27
|
+
}
|
|
28
|
+
files.each do |file|
|
|
29
|
+
puts "Fetching #{file} ..."
|
|
30
|
+
url = "#{base}#{file}"
|
|
31
|
+
rsp = Faraday.get(url)
|
|
32
|
+
unless rsp.status == 200
|
|
33
|
+
$stderr.puts "failing fetching: #{url}"
|
|
34
|
+
$stderr.puts " response: #{rsp.status}: #{rsp.body}"
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
File.open(file, "w") do |fh|
|
|
38
|
+
fh.write(rsp.body)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "Updates the changelog"
|
|
44
|
+
task :changelog do
|
|
45
|
+
sh "github_changelog_generator payrollhero/ph_utility --simple-list"
|
|
46
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'active_support/all'
|
|
2
|
+
require 'active_record'
|
|
3
|
+
require 'active_job'
|
|
4
|
+
require 'ph_model'
|
|
5
|
+
|
|
6
|
+
module WebhookSystem
|
|
7
|
+
extend ActiveSupport::Autoload
|
|
8
|
+
|
|
9
|
+
autoload :Subscription
|
|
10
|
+
autoload :Dispatcher
|
|
11
|
+
autoload :SubscriptionTopic
|
|
12
|
+
autoload :Job
|
|
13
|
+
autoload :Encoder
|
|
14
|
+
autoload :BaseEvent
|
|
15
|
+
|
|
16
|
+
# Error raised when there is an issue with decoding the payload
|
|
17
|
+
class DecodingError < RuntimeError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
delegate :dispatch, to: :'WebhookSystem::Dispatcher'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module WebhookSystem
|
|
2
|
+
|
|
3
|
+
# This is the class meant to be used as the base class for any Events sent through the Webhook system
|
|
4
|
+
class BaseEvent
|
|
5
|
+
include PhModel
|
|
6
|
+
|
|
7
|
+
def event_name
|
|
8
|
+
mesg = "class #{self.class.name} must implement abstract method `#{self.class.name}#event_name()'."
|
|
9
|
+
raise RuntimeError.new(mesg).tap { |err| err.backtrace = caller }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def payload_attributes
|
|
13
|
+
mesg = "class #{self.class.name} must implement abstract method `#{self.class.name}#payload_attributes()'."
|
|
14
|
+
raise RuntimeError.new(mesg).tap { |err| err.backtrace = caller }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def as_json
|
|
18
|
+
result = { 'event' => event_name }
|
|
19
|
+
each_attribute do |attribute_name, attribute_method|
|
|
20
|
+
result[attribute_name.to_s] = public_send(attribute_method).as_json
|
|
21
|
+
end
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def each_attribute
|
|
28
|
+
case payload_attributes
|
|
29
|
+
when Array
|
|
30
|
+
payload_attributes.each do |attribute_name|
|
|
31
|
+
yield(attribute_name, attribute_name)
|
|
32
|
+
end
|
|
33
|
+
when Hash
|
|
34
|
+
payload_attributes.each
|
|
35
|
+
else
|
|
36
|
+
raise ArgumetError, "don't know how to deal with payload_attributes: #{payload_attributes.inspect}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module WebhookSystem
|
|
2
|
+
|
|
3
|
+
# Main code that handles dispatching of events out to subscribers
|
|
4
|
+
class Dispatcher
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
# @param [WebhookSystem::BaseEvent] event The Event Object
|
|
8
|
+
def dispatch(event)
|
|
9
|
+
WebhookSystem::Subscription.interested_in_topic(event.event_name).each do |subscription|
|
|
10
|
+
WebhookSystem::Job.perform_later subscription, event.as_json
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module WebhookSystem
|
|
2
|
+
|
|
3
|
+
# Class in charge of encoding and decoding encrypted payload
|
|
4
|
+
module Encoder
|
|
5
|
+
class << self
|
|
6
|
+
# Given a secret string, encode the passed payload to json
|
|
7
|
+
# encrypt it, base64 encode that, and wrap it in its own json wrapper
|
|
8
|
+
#
|
|
9
|
+
# @param [String] secret_string some secret string
|
|
10
|
+
# @param [Object#to_json] payload Any object that responds to to_json
|
|
11
|
+
# @return [String] The encoded string payload (its a JSON string)
|
|
12
|
+
def encode(secret_string, payload)
|
|
13
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
|
14
|
+
cipher.encrypt
|
|
15
|
+
iv = cipher.random_iv
|
|
16
|
+
cipher.key = key_from_secret(iv, secret_string)
|
|
17
|
+
encoded = cipher.update(payload.to_json) + cipher.final
|
|
18
|
+
Payload.encode(encoded, iv)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Given a secret string, and an encrypted payload, unwrap it, bas64 decode it
|
|
22
|
+
# decrypt it, and JSON decode it
|
|
23
|
+
#
|
|
24
|
+
# @param [String] secret_string some secret string
|
|
25
|
+
# @param [String] payload String as returned from #encode
|
|
26
|
+
# @return [Object] return the JSON decode of the encrypted payload
|
|
27
|
+
def decode(secret_string, payload)
|
|
28
|
+
encoded, iv = Payload.decode(payload)
|
|
29
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
|
30
|
+
cipher.decrypt
|
|
31
|
+
cipher.iv = iv
|
|
32
|
+
cipher.key = key_from_secret(iv, secret_string)
|
|
33
|
+
decoded = cipher.update(encoded) + cipher.final
|
|
34
|
+
JSON.load(decoded)
|
|
35
|
+
rescue OpenSSL::Cipher::CipherError
|
|
36
|
+
raise DecodingError, 'Decoding Failed, probably mismatched secret'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def key_from_secret(iv, secret_string)
|
|
42
|
+
OpenSSL::PKCS5.pbkdf2_hmac(secret_string, iv, 100_000, 256 / 8, 'SHA256')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# private class to just wrap the outer wrapping of the response format
|
|
48
|
+
# not exposed to the outside
|
|
49
|
+
# :nodoc:
|
|
50
|
+
module Payload
|
|
51
|
+
class << self
|
|
52
|
+
def encode(raw_encrypted_data, iv)
|
|
53
|
+
JSON.dump(
|
|
54
|
+
'format' => 'base64+aes256',
|
|
55
|
+
'payload' => Base64.encode64(raw_encrypted_data),
|
|
56
|
+
'iv' => Base64.encode64(iv)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def decode(payload_string)
|
|
61
|
+
payload = JSON.load(payload_string)
|
|
62
|
+
unless payload['format'] == 'base64+aes256'
|
|
63
|
+
raise ArgumentError, 'only know how to handle base64+aes256 payloads'
|
|
64
|
+
end
|
|
65
|
+
[Base64.decode64(payload['payload']), Base64.decode64(payload['iv'])]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module WebhookSystem
|
|
2
|
+
|
|
3
|
+
# This is the ActiveJob in charge of actually sending each event
|
|
4
|
+
class Job < ActiveJob::Base
|
|
5
|
+
|
|
6
|
+
def perform(subscription, event)
|
|
7
|
+
payload = Encoder.encode(subscription.secret, event)
|
|
8
|
+
client = HttpClient.new(subscription.url)
|
|
9
|
+
client.post(payload)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Just a simple internal class to wrap around the http requests to the endpoints
|
|
15
|
+
class HttpClient
|
|
16
|
+
def initialize(endpoint)
|
|
17
|
+
@endpoint = endpoint
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def post(payload)
|
|
21
|
+
client.post do |req|
|
|
22
|
+
req.headers['Content-Type'] = 'application/json; base64+aes256'
|
|
23
|
+
req.body = payload.to_s
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :endpoint, :client
|
|
30
|
+
|
|
31
|
+
def client
|
|
32
|
+
@client ||= Faraday.new(url: endpoint) do |faraday|
|
|
33
|
+
# faraday.request :url_encoded # form-encode POST params
|
|
34
|
+
faraday.response :logger # log requests to STDOUT
|
|
35
|
+
faraday.adapter Faraday.default_adapter
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module WebhookSystem
|
|
2
|
+
class Subscription < ActiveRecord::Base
|
|
3
|
+
self.table_name = 'webhook_subscriptions'
|
|
4
|
+
|
|
5
|
+
validates :url, presence: true
|
|
6
|
+
validates :secret, presence: true
|
|
7
|
+
|
|
8
|
+
has_many :topics, class_name: 'WebhookSystem::SubscriptionTopic', dependent: :destroy
|
|
9
|
+
|
|
10
|
+
scope :active, -> { where(active: true) }
|
|
11
|
+
scope :for_topic, -> (topic) {
|
|
12
|
+
joins(:topics).where(WebhookSystem::SubscriptionTopic.table_name => { name: topic })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
scope :interested_in_topic, -> (topic) { active.for_topic(topic) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require File.expand_path('../lib/webhook_system/version', __FILE__)
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |gem|
|
|
6
|
+
gem.name = 'webhook_system'
|
|
7
|
+
gem.version = WebhookSystem::VERSION
|
|
8
|
+
gem.authors = ['Piotr Banasik']
|
|
9
|
+
gem.email = 'piotr@payrollhero.com'
|
|
10
|
+
|
|
11
|
+
gem.summary = 'Webhook system'
|
|
12
|
+
gem.description = 'A pluggable webhook subscription system'
|
|
13
|
+
gem.homepage = 'https://github.com/payrollhero/webhook_system'
|
|
14
|
+
gem.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
gem.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
gem.bindir = 'exe'
|
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
19
|
+
gem.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
gem.add_runtime_dependency 'activesupport', '> 3.2'
|
|
22
|
+
gem.add_runtime_dependency 'activerecord', '> 3.2'
|
|
23
|
+
gem.add_runtime_dependency 'activejob'
|
|
24
|
+
gem.add_runtime_dependency 'faraday'
|
|
25
|
+
gem.add_runtime_dependency 'ph_model'
|
|
26
|
+
|
|
27
|
+
gem.add_development_dependency 'bundler', '~> 1.0'
|
|
28
|
+
gem.add_development_dependency 'rake'
|
|
29
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
|
30
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
|
31
|
+
gem.add_development_dependency 'pry'
|
|
32
|
+
gem.add_development_dependency 'sqlite3'
|
|
33
|
+
gem.add_development_dependency 'github_changelog_generator', '~> 1.6'
|
|
34
|
+
gem.add_development_dependency 'factory_girl'
|
|
35
|
+
gem.add_development_dependency 'webmock'
|
|
36
|
+
|
|
37
|
+
# static analysis gems
|
|
38
|
+
gem.add_development_dependency 'rubocop', '~> 0.36.0'
|
|
39
|
+
gem.add_development_dependency 'reek', '~> 3.7'
|
|
40
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: webhook_system
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Piotr Banasik
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-02-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activejob
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: faraday
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: ph_model
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: bundler
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rake
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rspec
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '3.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubygems-tasks
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0.2'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0.2'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: pry
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: sqlite3
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: github_changelog_generator
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '1.6'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '1.6'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: factory_girl
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0'
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - ">="
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '0'
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: webmock
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :development
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - ">="
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
209
|
+
- !ruby/object:Gem::Dependency
|
|
210
|
+
name: rubocop
|
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
|
212
|
+
requirements:
|
|
213
|
+
- - "~>"
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: 0.36.0
|
|
216
|
+
type: :development
|
|
217
|
+
prerelease: false
|
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - "~>"
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: 0.36.0
|
|
223
|
+
- !ruby/object:Gem::Dependency
|
|
224
|
+
name: reek
|
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
|
226
|
+
requirements:
|
|
227
|
+
- - "~>"
|
|
228
|
+
- !ruby/object:Gem::Version
|
|
229
|
+
version: '3.7'
|
|
230
|
+
type: :development
|
|
231
|
+
prerelease: false
|
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - "~>"
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: '3.7'
|
|
237
|
+
description: A pluggable webhook subscription system
|
|
238
|
+
email: piotr@payrollhero.com
|
|
239
|
+
executables: []
|
|
240
|
+
extensions: []
|
|
241
|
+
extra_rdoc_files: []
|
|
242
|
+
files:
|
|
243
|
+
- ".codeclimate.yml"
|
|
244
|
+
- ".gitignore"
|
|
245
|
+
- ".hound.yml"
|
|
246
|
+
- ".reek"
|
|
247
|
+
- ".rubocop.hound.yml"
|
|
248
|
+
- ".rubocop.yml"
|
|
249
|
+
- ".travis.yml"
|
|
250
|
+
- CHANGELOG.md
|
|
251
|
+
- DEPLOYING.md
|
|
252
|
+
- Gemfile
|
|
253
|
+
- LICENSE.txt
|
|
254
|
+
- README.md
|
|
255
|
+
- Rakefile
|
|
256
|
+
- lib/webhook_system.rb
|
|
257
|
+
- lib/webhook_system/base_event.rb
|
|
258
|
+
- lib/webhook_system/dispatcher.rb
|
|
259
|
+
- lib/webhook_system/encoder.rb
|
|
260
|
+
- lib/webhook_system/job.rb
|
|
261
|
+
- lib/webhook_system/subscription.rb
|
|
262
|
+
- lib/webhook_system/subscription_topic.rb
|
|
263
|
+
- lib/webhook_system/version.rb
|
|
264
|
+
- webhook_system.gemspec
|
|
265
|
+
homepage: https://github.com/payrollhero/webhook_system
|
|
266
|
+
licenses:
|
|
267
|
+
- MIT
|
|
268
|
+
metadata: {}
|
|
269
|
+
post_install_message:
|
|
270
|
+
rdoc_options: []
|
|
271
|
+
require_paths:
|
|
272
|
+
- lib
|
|
273
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
274
|
+
requirements:
|
|
275
|
+
- - ">="
|
|
276
|
+
- !ruby/object:Gem::Version
|
|
277
|
+
version: '0'
|
|
278
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
279
|
+
requirements:
|
|
280
|
+
- - ">="
|
|
281
|
+
- !ruby/object:Gem::Version
|
|
282
|
+
version: '0'
|
|
283
|
+
requirements: []
|
|
284
|
+
rubyforge_project:
|
|
285
|
+
rubygems_version: 2.4.5.1
|
|
286
|
+
signing_key:
|
|
287
|
+
specification_version: 4
|
|
288
|
+
summary: Webhook system
|
|
289
|
+
test_files: []
|