bullet_train-outgoing_webhooks 1.0.2 → 1.0.5
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/MIT-LICENSE +1 -1
- data/app/assets/config/{bullet_train_outgoing_webhooks_web_manifest.js → bullet_train_outgoing_webhooks_manifest.js} +0 -0
- data/app/controllers/account/webhooks/outgoing/endpoints_controller.rb +4 -3
- data/app/controllers/api/v1/webhooks/outgoing/deliveries_endpoint.rb +63 -0
- data/app/controllers/api/v1/webhooks/outgoing/delivery_attempts_endpoint.rb +65 -0
- data/app/controllers/api/v1/webhooks/outgoing/endpoints_endpoint.rb +119 -0
- data/app/jobs/webhooks/outgoing/delivery_job.rb +7 -0
- data/app/jobs/webhooks/outgoing/generate_job.rb +7 -0
- data/app/models/concerns/webhooks/outgoing/delivery_attempt_support.rb +64 -0
- data/app/models/concerns/webhooks/outgoing/delivery_support.rb +66 -0
- data/app/models/concerns/webhooks/outgoing/endpoint_support.rb +37 -0
- data/app/models/concerns/webhooks/outgoing/event_support.rb +45 -0
- data/app/models/concerns/webhooks/outgoing/issuing_model.rb +67 -0
- data/app/models/concerns/webhooks/outgoing/team_support.rb +31 -0
- data/app/models/concerns/webhooks/outgoing/uri_filtering.rb +149 -0
- data/app/models/webhooks/outgoing/delivery.rb +20 -0
- data/app/models/webhooks/outgoing/delivery_attempt.rb +20 -0
- data/app/models/webhooks/outgoing/endpoint.rb +20 -0
- data/app/models/webhooks/outgoing/event.rb +3 -0
- data/app/models/webhooks/outgoing/event_type.rb +13 -0
- data/app/models/webhooks/outgoing.rb +5 -0
- data/app/models/webhooks.rb +5 -0
- data/app/serializers/api/v1/webhooks/outgoing/delivery_attempt_serializer.rb +16 -0
- data/app/serializers/api/v1/webhooks/outgoing/delivery_serializer.rb +14 -0
- data/app/serializers/api/v1/webhooks/outgoing/endpoint_serializer.rb +14 -0
- data/app/views/account/webhooks/outgoing/deliveries/_menu_item.html.erb +2 -2
- data/app/views/account/webhooks/outgoing/delivery_attempts/_menu_item.html.erb +2 -2
- data/app/views/account/webhooks/outgoing/endpoints/_breadcrumbs.html.erb +1 -1
- data/app/views/account/webhooks/outgoing/endpoints/_form.html.erb +2 -2
- data/app/views/account/webhooks/outgoing/endpoints/_index.html.erb +3 -3
- data/app/views/account/webhooks/outgoing/endpoints/_menu_item.html.erb +2 -2
- data/app/views/account/webhooks/outgoing/endpoints/show.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/db/migrate/20180420013127_create_webhooks_outgoing_endpoints.rb +10 -0
- data/db/migrate/20180420013505_create_webhooks_outgoing_endpoint_event_types.rb +10 -0
- data/db/migrate/20180420013852_create_webhooks_outgoing_event_types.rb +9 -0
- data/db/migrate/20180420014623_create_webhooks_outgoing_events.rb +12 -0
- data/db/migrate/20180420021016_create_webhooks_outgoing_deliveries.rb +11 -0
- data/db/migrate/20180420022027_add_team_to_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20180420165345_create_webhooks_outgoing_delivery_attempts.rb +11 -0
- data/db/migrate/20180420172112_add_error_message_to_webhooks_outgoing_delivery_attempt.rb +6 -0
- data/db/migrate/20181012234232_add_name_to_webhooks_outgoing_endpoints.rb +5 -0
- data/db/migrate/20181013030208_add_delivered_at_to_webhooks_outgoing_deliveries.rb +5 -0
- data/db/migrate/20181013165056_add_uuid_to_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20181013174539_add_payload_to_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20181013192951_add_attempt_number_to_webhooks_outgoing_delivery_attempts.rb +5 -0
- data/db/migrate/20210805060451_change_body_to_data_on_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20211126230846_add_event_type_ids_to_webhooks_outgoing_endpoints.rb +5 -0
- data/db/migrate/20211126230847_migrate_event_type_ids_on_webhooks_outgoing_endpoints.rb +8 -0
- data/db/migrate/20211127013539_remove_event_type_from_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20211127013800_add_new_event_type_id_to_webhooks_outgoing_events.rb +5 -0
- data/db/migrate/20211127014001_migrate_event_types_on_webhooks_outgoing_events.rb +10 -0
- data/db/migrate/20211127015712_drop_webhooks_outgoing_endpoint_event_types.rb +9 -0
- data/db/migrate/20211127015713_drop_webhooks_outgoing_event_types.rb +9 -0
- data/lib/bullet_train/outgoing_webhooks/engine.rb +32 -0
- data/lib/bullet_train/outgoing_webhooks/version.rb +1 -1
- data/lib/bullet_train/outgoing_webhooks.rb +28 -3
- metadata +81 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4d3884adf8d199b92ccf09ca749e8237e86a421224de5b8b81cf5ab0d9f4329
|
4
|
+
data.tar.gz: cec7b468644c484f8bcb0f4064b74764ab3e7b8873cb72ba5c8944009b95a576
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98d7265e65165a0c2b55a0b2880063f00c72292e0a461e4bde667d54f077860f1b4861873f5131bd203e45b85ab03868822970071e626c34e2ed640059e5cc11
|
7
|
+
data.tar.gz: f36d57ccd33ba9268a106ef22c1457560f9b171ff0533b4f8102301e5962691f827ffa437c7f13d3fd7ab847b1b626a9c6374cca45efbbcab228caf7c265d2a9
|
data/MIT-LICENSE
CHANGED
File without changes
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Account::Webhooks::Outgoing::EndpointsController < Account::ApplicationController
|
2
|
-
account_load_and_authorize_resource :endpoint, through:
|
2
|
+
account_load_and_authorize_resource :endpoint, through: BulletTrain::OutgoingWebhooks.parent_association, through_association: :webhooks_outgoing_endpoints
|
3
|
+
before_action { @parent = instance_variable_get("@#{BulletTrain::OutgoingWebhooks.parent_association}") }
|
3
4
|
|
4
5
|
# GET /account/teams/:team_id/webhooks/outgoing/endpoints
|
5
6
|
# GET /account/teams/:team_id/webhooks/outgoing/endpoints.json
|
@@ -26,7 +27,7 @@ class Account::Webhooks::Outgoing::EndpointsController < Account::ApplicationCon
|
|
26
27
|
def create
|
27
28
|
respond_to do |format|
|
28
29
|
if @endpoint.save
|
29
|
-
format.html { redirect_to [:account, @
|
30
|
+
format.html { redirect_to [:account, @parent, :webhooks_outgoing_endpoints], notice: I18n.t("webhooks/outgoing/endpoints.notifications.created") }
|
30
31
|
format.json { render :show, status: :created, location: [:account, @endpoint] }
|
31
32
|
else
|
32
33
|
format.html { render :new, status: :unprocessable_entity }
|
@@ -54,7 +55,7 @@ class Account::Webhooks::Outgoing::EndpointsController < Account::ApplicationCon
|
|
54
55
|
def destroy
|
55
56
|
@endpoint.destroy
|
56
57
|
respond_to do |format|
|
57
|
-
format.html { redirect_to [:account, @
|
58
|
+
format.html { redirect_to [:account, @parent, :webhooks_outgoing_endpoints], notice: I18n.t("webhooks/outgoing/endpoints.notifications.destroyed") }
|
58
59
|
format.json { head :no_content }
|
59
60
|
end
|
60
61
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Api::V1::Webhooks::Outgoing::DeliveriesEndpoint < Api::V1::Root
|
2
|
+
helpers do
|
3
|
+
params :endpoint_id do
|
4
|
+
requires :endpoint_id, type: Integer, allow_blank: false, desc: "Endpoint ID"
|
5
|
+
end
|
6
|
+
|
7
|
+
params :id do
|
8
|
+
requires :id, type: Integer, allow_blank: false, desc: "Delivery ID"
|
9
|
+
end
|
10
|
+
|
11
|
+
params :delivery do
|
12
|
+
optional :event_id, type: String, desc: Api.heading(:event_id)
|
13
|
+
optional :endpoint_url, type: String, desc: Api.heading(:endpoint_url)
|
14
|
+
optional :delivered_at, type: DateTime, desc: Api.heading(:delivered_at)
|
15
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
16
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
17
|
+
|
18
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
resource "webhooks/outgoing/endpoints", desc: Api.title(:collection_actions) do
|
23
|
+
after_validation do
|
24
|
+
load_and_authorize_api_resource Webhooks::Outgoing::Delivery
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# INDEX
|
29
|
+
#
|
30
|
+
|
31
|
+
desc Api.title(:index), &Api.index_desc
|
32
|
+
params do
|
33
|
+
use :endpoint_id
|
34
|
+
end
|
35
|
+
oauth2
|
36
|
+
paginate per_page: 100
|
37
|
+
get "/:endpoint_id/deliveries" do
|
38
|
+
@paginated_deliveries = paginate @deliveries
|
39
|
+
render @paginated_deliveries, serializer: Api.serializer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
resource "webhooks/outgoing/deliveries", desc: Api.title(:member_actions) do
|
44
|
+
after_validation do
|
45
|
+
load_and_authorize_api_resource Webhooks::Outgoing::Delivery
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# SHOW
|
50
|
+
#
|
51
|
+
|
52
|
+
desc Api.title(:show), &Api.show_desc
|
53
|
+
params do
|
54
|
+
use :id
|
55
|
+
end
|
56
|
+
oauth2
|
57
|
+
route_param :id do
|
58
|
+
get do
|
59
|
+
render @delivery, serializer: Api.serializer
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Api::V1::Webhooks::Outgoing::DeliveryAttemptsEndpoint < Api::V1::Root
|
2
|
+
helpers do
|
3
|
+
params :delivery_id do
|
4
|
+
requires :delivery_id, type: Integer, allow_blank: false, desc: "Delivery ID"
|
5
|
+
end
|
6
|
+
|
7
|
+
params :id do
|
8
|
+
requires :id, type: Integer, allow_blank: false, desc: "Delivery Attempt ID"
|
9
|
+
end
|
10
|
+
|
11
|
+
params :delivery_attempt do
|
12
|
+
optional :response_code, type: String, desc: Api.heading(:response_code)
|
13
|
+
optional :response_body, type: String, desc: Api.heading(:response_body)
|
14
|
+
optional :response_message, type: String, desc: Api.heading(:response_message)
|
15
|
+
optional :error_message, type: String, desc: Api.heading(:error_message)
|
16
|
+
optional :attempt_number, type: String, desc: Api.heading(:attempt_number)
|
17
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
18
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
19
|
+
|
20
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
resource "webhooks/outgoing/deliveries", desc: Api.title(:collection_actions) do
|
25
|
+
after_validation do
|
26
|
+
load_and_authorize_api_resource Webhooks::Outgoing::DeliveryAttempt
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# INDEX
|
31
|
+
#
|
32
|
+
|
33
|
+
desc Api.title(:index), &Api.index_desc
|
34
|
+
params do
|
35
|
+
use :delivery_id
|
36
|
+
end
|
37
|
+
oauth2
|
38
|
+
paginate per_page: 100
|
39
|
+
get "/:delivery_id/delivery_attempts" do
|
40
|
+
@paginated_delivery_attempts = paginate @delivery_attempts
|
41
|
+
render @paginated_delivery_attempts, serializer: Api.serializer
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
resource "webhooks/outgoing/delivery_attempts", desc: Api.title(:member_actions) do
|
46
|
+
after_validation do
|
47
|
+
load_and_authorize_api_resource Webhooks::Outgoing::DeliveryAttempt
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# SHOW
|
52
|
+
#
|
53
|
+
|
54
|
+
desc Api.title(:show), &Api.show_desc
|
55
|
+
params do
|
56
|
+
use :id
|
57
|
+
end
|
58
|
+
oauth2
|
59
|
+
route_param :id do
|
60
|
+
get do
|
61
|
+
render @delivery_attempt, serializer: Api.serializer
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class Api::V1::Webhooks::Outgoing::EndpointsEndpoint < Api::V1::Root
|
2
|
+
helpers do
|
3
|
+
params BulletTrain::OutgoingWebhooks.parent_association_id do
|
4
|
+
requires BulletTrain::OutgoingWebhooks.parent_association_id, type: Integer, allow_blank: false, desc: "#{BulletTrain::OutgoingWebhooks.parent_class} ID"
|
5
|
+
end
|
6
|
+
|
7
|
+
params :id do
|
8
|
+
requires :id, type: Integer, allow_blank: false, desc: "Endpoint ID"
|
9
|
+
end
|
10
|
+
|
11
|
+
params :endpoint do
|
12
|
+
optional :name, type: String, desc: Api.heading(:name)
|
13
|
+
optional :url, type: String, desc: Api.heading(:url)
|
14
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
15
|
+
optional :event_type_ids, type: Array, desc: Api.heading(:event_type_ids)
|
16
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
17
|
+
|
18
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
resource BulletTrain::OutgoingWebhooks.parent_resource, desc: Api.title(:collection_actions) do
|
23
|
+
after_validation do
|
24
|
+
load_and_authorize_api_resource Webhooks::Outgoing::Endpoint
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# INDEX
|
29
|
+
#
|
30
|
+
|
31
|
+
desc Api.title(:index), &Api.index_desc
|
32
|
+
params do
|
33
|
+
use BulletTrain::OutgoingWebhooks.parent_association_id
|
34
|
+
end
|
35
|
+
oauth2
|
36
|
+
paginate per_page: 100
|
37
|
+
get "/:#{BulletTrain::OutgoingWebhooks.parent_association_id}/webhooks/outgoing/endpoints" do
|
38
|
+
@paginated_endpoints = paginate @endpoints
|
39
|
+
render @paginated_endpoints, serializer: Api.serializer
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# CREATE
|
44
|
+
#
|
45
|
+
|
46
|
+
desc Api.title(:create), &Api.create_desc
|
47
|
+
params do
|
48
|
+
use BulletTrain::OutgoingWebhooks.parent_association_id
|
49
|
+
use :endpoint
|
50
|
+
end
|
51
|
+
route_setting :api_resource_options, permission: :create
|
52
|
+
oauth2 "write"
|
53
|
+
post "/:#{BulletTrain::OutgoingWebhooks.parent_association_id}/webhooks/outgoing/endpoints" do
|
54
|
+
if @endpoint.save
|
55
|
+
render @endpoint, serializer: Api.serializer
|
56
|
+
else
|
57
|
+
record_not_saved @endpoint
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
resource "webhooks/outgoing/endpoints", desc: Api.title(:member_actions) do
|
63
|
+
after_validation do
|
64
|
+
load_and_authorize_api_resource Webhooks::Outgoing::Endpoint
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# SHOW
|
69
|
+
#
|
70
|
+
|
71
|
+
desc Api.title(:show), &Api.show_desc
|
72
|
+
params do
|
73
|
+
use :id
|
74
|
+
end
|
75
|
+
oauth2
|
76
|
+
route_param :id do
|
77
|
+
get do
|
78
|
+
render @endpoint, serializer: Api.serializer
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# UPDATE
|
84
|
+
#
|
85
|
+
|
86
|
+
desc Api.title(:update), &Api.update_desc
|
87
|
+
params do
|
88
|
+
use :id
|
89
|
+
use :endpoint
|
90
|
+
end
|
91
|
+
route_setting :api_resource_options, permission: :update
|
92
|
+
oauth2 "write"
|
93
|
+
route_param :id do
|
94
|
+
put do
|
95
|
+
if @endpoint.update(declared(params, include_missing: false))
|
96
|
+
render @endpoint, serializer: Api.serializer
|
97
|
+
else
|
98
|
+
record_not_saved @endpoint
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# DESTROY
|
105
|
+
#
|
106
|
+
|
107
|
+
desc Api.title(:destroy), &Api.destroy_desc
|
108
|
+
params do
|
109
|
+
use :id
|
110
|
+
end
|
111
|
+
route_setting :api_resource_options, permission: :destroy
|
112
|
+
oauth2 "delete"
|
113
|
+
route_param :id do
|
114
|
+
delete do
|
115
|
+
render @endpoint.destroy, serializer: Api.serializer
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Webhooks::Outgoing::DeliveryAttemptSupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include Webhooks::Outgoing::UriFiltering
|
4
|
+
|
5
|
+
SUCCESS_RESPONSE_CODES = [200, 201, 202, 203, 204, 205, 206, 207, 226].freeze
|
6
|
+
|
7
|
+
included do
|
8
|
+
belongs_to :delivery
|
9
|
+
has_one :team, through: :delivery unless BulletTrain::OutgoingWebhooks.parent_class_specified?
|
10
|
+
scope :successful, -> { where(response_code: SUCCESS_RESPONSE_CODES) }
|
11
|
+
|
12
|
+
before_create do
|
13
|
+
self.attempt_number = delivery.attempt_count + 1
|
14
|
+
end
|
15
|
+
|
16
|
+
validates :response_code, presence: true
|
17
|
+
end
|
18
|
+
|
19
|
+
def still_attempting?
|
20
|
+
error_message.nil? && response_code.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def successful?
|
24
|
+
SUCCESS_RESPONSE_CODES.include?(response_code)
|
25
|
+
end
|
26
|
+
|
27
|
+
def failed?
|
28
|
+
!(successful? || still_attempting?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def attempt
|
32
|
+
uri = URI.parse(delivery.endpoint_url)
|
33
|
+
|
34
|
+
unless allowed_uri?(uri)
|
35
|
+
self.response_code = 0
|
36
|
+
self.error_message = "URI is not allowed: " + uri
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
http = Net::HTTP.new(resolve_ip_from_authoritative(uri.hostname.downcase), uri.port)
|
41
|
+
http.use_ssl = true if uri.scheme == "https"
|
42
|
+
request = Net::HTTP::Post.new(uri.path)
|
43
|
+
request.add_field("Host", uri.host)
|
44
|
+
request.add_field("Content-Type", "application/json")
|
45
|
+
request.body = delivery.event.payload.to_json
|
46
|
+
|
47
|
+
begin
|
48
|
+
response = http.request(request)
|
49
|
+
self.response_message = response.message
|
50
|
+
self.response_code = response.code
|
51
|
+
self.response_body = response.body
|
52
|
+
rescue => exception
|
53
|
+
self.response_code = 0
|
54
|
+
self.error_message = exception.message
|
55
|
+
end
|
56
|
+
|
57
|
+
save
|
58
|
+
successful?
|
59
|
+
end
|
60
|
+
|
61
|
+
def label_string
|
62
|
+
"#{attempt_number.ordinalize} Attempt"
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Webhooks::Outgoing::DeliverySupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
belongs_to :endpoint, class_name: "Webhooks::Outgoing::Endpoint"
|
6
|
+
belongs_to :event, class_name: "Webhooks::Outgoing::Event"
|
7
|
+
|
8
|
+
has_one :team, through: :endpoint unless BulletTrain::OutgoingWebhooks.parent_class_specified?
|
9
|
+
has_many :delivery_attempts, class_name: "Webhooks::Outgoing::DeliveryAttempt", dependent: :destroy, foreign_key: :delivery_id
|
10
|
+
end
|
11
|
+
|
12
|
+
ATTEMPT_SCHEDULE = {
|
13
|
+
1 => 15.seconds,
|
14
|
+
2 => 1.minute,
|
15
|
+
3 => 5.minutes,
|
16
|
+
4 => 15.minutes,
|
17
|
+
5 => 1.hour,
|
18
|
+
}
|
19
|
+
|
20
|
+
def label_string
|
21
|
+
event.short_uuid
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_reattempt_delay
|
25
|
+
ATTEMPT_SCHEDULE[attempt_count]
|
26
|
+
end
|
27
|
+
|
28
|
+
def deliver_async
|
29
|
+
if still_attempting?
|
30
|
+
Webhooks::Outgoing::DeliveryJob.set(wait: next_reattempt_delay).perform_later(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def deliver
|
35
|
+
if delivery_attempts.create.attempt
|
36
|
+
touch(:delivered_at)
|
37
|
+
else
|
38
|
+
deliver_async
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def attempt_count
|
43
|
+
delivery_attempts.count
|
44
|
+
end
|
45
|
+
|
46
|
+
def delivered?
|
47
|
+
delivered_at.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
def still_attempting?
|
51
|
+
return false if delivered?
|
52
|
+
attempt_count < max_attempts
|
53
|
+
end
|
54
|
+
|
55
|
+
def failed?
|
56
|
+
!(delivered? || still_attempting?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def name
|
60
|
+
event.short_uuid
|
61
|
+
end
|
62
|
+
|
63
|
+
def max_attempts
|
64
|
+
ATTEMPT_SCHEDULE.keys.max
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Webhooks::Outgoing::EndpointSupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include Webhooks::Outgoing::UriFiltering
|
4
|
+
|
5
|
+
included do
|
6
|
+
belongs_to BulletTrain::OutgoingWebhooks.parent_association
|
7
|
+
|
8
|
+
has_many :deliveries, class_name: "Webhooks::Outgoing::Delivery", dependent: :destroy, foreign_key: :endpoint_id
|
9
|
+
has_many :events, -> { distinct }, through: :deliveries
|
10
|
+
|
11
|
+
scope :listening_for_event_type_id, ->(event_type_id) { where("event_type_ids @> ? OR event_type_ids = '[]'::jsonb", "\"#{event_type_id}\"") }
|
12
|
+
|
13
|
+
validates :name, presence: true
|
14
|
+
|
15
|
+
before_validation { url&.strip! }
|
16
|
+
|
17
|
+
validates :url, presence: true, allowed_uri: true
|
18
|
+
|
19
|
+
after_initialize do
|
20
|
+
self.event_type_ids ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
after_save :touch_parent
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid_event_types
|
27
|
+
Webhooks::Outgoing::EventType.all
|
28
|
+
end
|
29
|
+
|
30
|
+
def event_types
|
31
|
+
event_type_ids.map { |id| Webhooks::Outgoing::EventType.find(id) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def touch_parent
|
35
|
+
send(BulletTrain::OutgoingWebhooks.parent_association).touch
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Webhooks::Outgoing::EventSupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include HasUuid
|
4
|
+
|
5
|
+
included do
|
6
|
+
belongs_to BulletTrain::OutgoingWebhooks.parent_association
|
7
|
+
belongs_to :event_type, class_name: "Webhooks::Outgoing::EventType"
|
8
|
+
belongs_to :subject, polymorphic: true
|
9
|
+
has_many :deliveries, dependent: :destroy
|
10
|
+
|
11
|
+
before_create do
|
12
|
+
self.payload = generate_payload
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate_payload
|
17
|
+
{
|
18
|
+
event_id: uuid,
|
19
|
+
event_type: event_type_id,
|
20
|
+
subject_id: subject_id,
|
21
|
+
subject_type: subject_type,
|
22
|
+
data: data
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def event_type_name
|
27
|
+
payload.dig("event_type")
|
28
|
+
end
|
29
|
+
|
30
|
+
def endpoints
|
31
|
+
send(BulletTrain::OutgoingWebhooks.parent_association).webhooks_outgoing_endpoints.listening_for_event_type_id(event_type_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def deliver
|
35
|
+
endpoints.each do |endpoint|
|
36
|
+
unless endpoint.deliveries.where(event: self).any?
|
37
|
+
endpoint.deliveries.create(event: self, endpoint_url: endpoint.url).deliver_async
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def label_string
|
43
|
+
short_uuid
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Webhooks::Outgoing::IssuingModel
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# define relationships.
|
5
|
+
included do
|
6
|
+
after_commit :generate_created_webhook, on: [:create]
|
7
|
+
after_commit :generate_updated_webhook, on: [:update]
|
8
|
+
after_commit :generate_deleted_webhook, on: [:destroy]
|
9
|
+
has_many :webhooks_outgoing_events, as: :subject, class_name: "Webhooks::Outgoing::Event", dependent: :nullify
|
10
|
+
end
|
11
|
+
|
12
|
+
def skip_generate_webhook?(action)
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def parent
|
17
|
+
return unless respond_to? BulletTrain::OutgoingWebhooks.parent_association
|
18
|
+
send(BulletTrain::OutgoingWebhooks.parent_association)
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_webhook(action, async: true)
|
22
|
+
# allow individual models to opt out of generating webhooks
|
23
|
+
return if skip_generate_webhook?(action)
|
24
|
+
|
25
|
+
# we can only generate webhooks for objects that return their their team / parent.
|
26
|
+
return unless parent.present?
|
27
|
+
|
28
|
+
# Try to find an event type definition for this action.
|
29
|
+
event_type = Webhooks::Outgoing::EventType.find_by(id: "#{self.class.name.underscore}.#{action}")
|
30
|
+
|
31
|
+
# If the event type is defined as one that people can be subscribed to,
|
32
|
+
# and this object has a parent where an associated outgoing webhooks endpoint could be registered.
|
33
|
+
if event_type
|
34
|
+
# Only generate an event record if an endpoint is actually listening for this event type.
|
35
|
+
if parent.endpoints_listening_for_event_type?(event_type)
|
36
|
+
if async
|
37
|
+
# serialization can be heavy so run it as a job
|
38
|
+
Webhooks::Outgoing::GenerateJob.perform_later(self, action)
|
39
|
+
else
|
40
|
+
generate_webhook_perform(action)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_webhook_perform(action)
|
47
|
+
event_type = Webhooks::Outgoing::EventType.find_by(id: "#{self.class.name.underscore}.#{action}")
|
48
|
+
data = "Api::V1::#{self.class.name}Serializer".constantize.new(self).serializable_hash[:data]
|
49
|
+
webhook = send(BulletTrain::OutgoingWebhooks.parent_association).webhooks_outgoing_events.create(event_type_id: event_type.id, subject: self, data: data)
|
50
|
+
webhook.deliver
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_created_webhook
|
54
|
+
generate_webhook(:created)
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_updated_webhook
|
58
|
+
generate_webhook(:updated)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_deleted_webhook
|
62
|
+
return false unless parent.present?
|
63
|
+
return false if parent.being_destroyed?
|
64
|
+
|
65
|
+
generate_webhook(:deleted, async: false)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Webhooks::Outgoing::TeamSupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_many :webhooks_outgoing_endpoints, class_name: "Webhooks::Outgoing::Endpoint", dependent: :destroy
|
6
|
+
has_many :webhooks_outgoing_events, class_name: "Webhooks::Outgoing::Event", dependent: :destroy
|
7
|
+
|
8
|
+
before_destroy :mark_for_destruction, prepend: true
|
9
|
+
end
|
10
|
+
|
11
|
+
def should_cache_endpoints_listening_for_event_type?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def endpoints_listening_for_event_type?(event_type)
|
16
|
+
if should_cache_endpoints_listening_for_event_type?
|
17
|
+
key = "#{cache_key_with_version}/endpoints_for_event_type/#{event_type.cache_key}"
|
18
|
+
|
19
|
+
Rails.cache.fetch(key, expires_in: 24.hours, race_condition_ttl: 5.seconds) do
|
20
|
+
webhooks_outgoing_endpoints.listening_for_event_type_id(event_type.id).any?
|
21
|
+
end
|
22
|
+
else
|
23
|
+
webhooks_outgoing_endpoints.listening_for_event_type_id(event_type.id).any?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def mark_for_destruction
|
28
|
+
# This allows downstream logic to check whether a team is being destroyed in order to bypass webhook issuance.
|
29
|
+
update_column(:being_destroyed, true)
|
30
|
+
end
|
31
|
+
end
|