webhookdb 1.2.2 → 1.3.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 +4 -4
- data/admin-dist/assets/index-6aebf805.js +264 -0
- data/admin-dist/favicon.ico +0 -0
- data/admin-dist/index.html +130 -0
- data/admin-dist/manifest.json +15 -0
- data/data/messages/replicators/url-recorder.liquid +20 -0
- data/data/messages/templates/errors/signalwire_send_sms.email.liquid +31 -0
- data/data/messages/web/install-customer-login.liquid +6 -5
- data/data/messages/web/install-error.liquid +1 -1
- data/data/messages/web/install-forbidden.liquid +25 -0
- data/data/messages/web/install-org-chooser.liquid +40 -0
- data/data/messages/web/install-success.liquid +2 -1
- data/data/messages/web/install.liquid +2 -1
- data/data/messages/web/partials/head.liquid +2 -0
- data/data/messages/web/styles.liquid +24 -0
- data/db/migrations/041_views.rb +20 -0
- data/db/migrations/042_sint_lock.rb +10 -0
- data/db/migrations/043_text_search.rb +28 -0
- data/db/migrations/044_oauth_session_token_cache.rb +21 -0
- data/integration/auth_spec.rb +2 -2
- data/lib/sequel/plugins/text_searchable.rb +165 -0
- data/lib/sequel/text_searchable.rb +42 -0
- data/lib/webhookdb/admin_api/auth.rb +24 -3
- data/lib/webhookdb/admin_api/data_provider.rb +196 -0
- data/lib/webhookdb/admin_api/entities.rb +143 -28
- data/lib/webhookdb/admin_api.rb +0 -2
- data/lib/webhookdb/api/auth.rb +5 -6
- data/lib/webhookdb/api/db.rb +31 -6
- data/lib/webhookdb/api/entities.rb +7 -1
- data/lib/webhookdb/api/helpers.rb +6 -25
- data/lib/webhookdb/api/install.rb +204 -79
- data/lib/webhookdb/api/organizations.rb +14 -12
- data/lib/webhookdb/api/saved_queries.rb +9 -3
- data/lib/webhookdb/api/saved_views.rb +99 -0
- data/lib/webhookdb/api/service_integrations.rb +15 -9
- data/lib/webhookdb/api/subscriptions.rb +3 -1
- data/lib/webhookdb/api/sync_targets.rb +9 -7
- data/lib/webhookdb/api/system.rb +1 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
- data/lib/webhookdb/apps.rb +30 -7
- data/lib/webhookdb/async/audit_logger.rb +2 -0
- data/lib/webhookdb/async.rb +5 -0
- data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
- data/lib/webhookdb/backfill_job.rb +9 -0
- data/lib/webhookdb/customer.rb +5 -0
- data/lib/webhookdb/database_document.rb +1 -1
- data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
- data/lib/webhookdb/db_adapter.rb +20 -4
- data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
- data/lib/webhookdb/fixtures/organizations.rb +5 -0
- data/lib/webhookdb/fixtures/roles.rb +14 -0
- data/lib/webhookdb/fixtures/saved_views.rb +25 -0
- data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
- data/lib/webhookdb/http.rb +8 -2
- data/lib/webhookdb/icalendar.rb +3 -0
- data/lib/webhookdb/idempotency.rb +69 -22
- data/lib/webhookdb/increase.rb +69 -21
- data/lib/webhookdb/intercom.rb +10 -3
- data/lib/webhookdb/jobs/backfill.rb +3 -1
- data/lib/webhookdb/jobs/emailer.rb +0 -1
- data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
- data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
- data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
- data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
- data/lib/webhookdb/message/body.rb +6 -4
- data/lib/webhookdb/message/delivery.rb +2 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
- data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
- data/lib/webhookdb/oauth/fake_provider.rb +44 -0
- data/lib/webhookdb/oauth/front_provider.rb +1 -2
- data/lib/webhookdb/oauth/increase_provider.rb +80 -0
- data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
- data/lib/webhookdb/oauth/session.rb +20 -0
- data/lib/webhookdb/oauth.rb +7 -21
- data/lib/webhookdb/organization/alerting.rb +2 -0
- data/lib/webhookdb/organization/database_migration.rb +3 -0
- data/lib/webhookdb/organization.rb +37 -6
- data/lib/webhookdb/organization_membership.rb +14 -7
- data/lib/webhookdb/postgres.rb +2 -0
- data/lib/webhookdb/replicator/base.rb +1 -0
- data/lib/webhookdb/replicator/docgen.rb +9 -1
- data/lib/webhookdb/replicator/fake.rb +2 -3
- data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
- data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
- data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
- data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
- data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
- data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
- data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
- data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
- data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
- data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
- data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
- data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
- data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
- data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
- data/lib/webhookdb/replicator/webhook_request.rb +4 -0
- data/lib/webhookdb/replicator.rb +8 -0
- data/lib/webhookdb/role.rb +5 -2
- data/lib/webhookdb/saved_query.rb +23 -0
- data/lib/webhookdb/saved_view.rb +73 -0
- data/lib/webhookdb/sentry.rb +2 -0
- data/lib/webhookdb/service/entities.rb +0 -4
- data/lib/webhookdb/service/helpers.rb +5 -0
- data/lib/webhookdb/service/middleware.rb +9 -0
- data/lib/webhookdb/service/types.rb +10 -8
- data/lib/webhookdb/service/validators.rb +1 -2
- data/lib/webhookdb/service/view_api.rb +1 -1
- data/lib/webhookdb/service_integration.rb +17 -15
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
- data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
- data/lib/webhookdb/subscription.rb +2 -0
- data/lib/webhookdb/sync_target.rb +10 -2
- data/lib/webhookdb/tasks/message.rb +3 -1
- data/lib/webhookdb/version.rb +1 -1
- data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
- data/lib/webhookdb/webhook_subscription.rb +2 -0
- metadata +57 -9
- data/lib/webhookdb/admin_api/customers.rb +0 -63
- data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
- data/lib/webhookdb/admin_api/roles.rb +0 -15
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "webhookdb/increase"
|
|
4
3
|
require "webhookdb/replicator/increase_v1_mixin"
|
|
5
4
|
|
|
6
5
|
class Webhookdb::Replicator::IncreaseTransactionV1 < Webhookdb::Replicator::Base
|
|
@@ -11,10 +10,10 @@ class Webhookdb::Replicator::IncreaseTransactionV1 < Webhookdb::Replicator::Base
|
|
|
11
10
|
def self.descriptor
|
|
12
11
|
return Webhookdb::Replicator::Descriptor.new(
|
|
13
12
|
name: "increase_transaction_v1",
|
|
14
|
-
ctor:
|
|
13
|
+
ctor: self,
|
|
15
14
|
feature_roles: [],
|
|
16
15
|
resource_name_singular: "Increase Transaction",
|
|
17
|
-
|
|
16
|
+
dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
|
|
18
17
|
supports_backfill: true,
|
|
19
18
|
api_docs_url: "https://increase.com/documentation/api",
|
|
20
19
|
)
|
|
@@ -28,13 +27,8 @@ class Webhookdb::Replicator::IncreaseTransactionV1 < Webhookdb::Replicator::Base
|
|
|
28
27
|
return [
|
|
29
28
|
Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
|
|
30
29
|
Webhookdb::Replicator::Column.new(:amount, INTEGER, index: true),
|
|
31
|
-
Webhookdb::Replicator::Column.new(
|
|
32
|
-
|
|
33
|
-
TIMESTAMP,
|
|
34
|
-
data_key: "created_at",
|
|
35
|
-
optional: true,
|
|
36
|
-
index: true,
|
|
37
|
-
),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
|
|
38
32
|
# date is a legacy field that is not documented in the API,
|
|
39
33
|
# but is still sent with transactions as of April 2022.
|
|
40
34
|
# We need to support the v1 schema, but do not want to depend
|
|
@@ -48,27 +42,8 @@ class Webhookdb::Replicator::IncreaseTransactionV1 < Webhookdb::Replicator::Base
|
|
|
48
42
|
converter: Webhookdb::Replicator::Column::CONV_TO_UTC_DATE,
|
|
49
43
|
),
|
|
50
44
|
Webhookdb::Replicator::Column.new(:route_id, TEXT, index: true),
|
|
51
|
-
Webhookdb::Replicator::Column.new(
|
|
52
|
-
:updated_at,
|
|
53
|
-
TIMESTAMP,
|
|
54
|
-
data_key: "created_at",
|
|
55
|
-
event_key: "created_at",
|
|
56
|
-
defaulter: :now,
|
|
57
|
-
optional: true,
|
|
58
|
-
index: true,
|
|
59
|
-
),
|
|
60
45
|
]
|
|
61
46
|
end
|
|
62
47
|
|
|
63
|
-
def
|
|
64
|
-
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def _resource_and_event(request)
|
|
68
|
-
return self._find_resource_and_event(request.body, "transaction")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def _mixin_backfill_url
|
|
72
|
-
return "#{self.service_integration.api_url}/transactions"
|
|
73
|
-
end
|
|
48
|
+
def _mixin_object_type = "transaction"
|
|
74
49
|
end
|
|
@@ -3,119 +3,99 @@
|
|
|
3
3
|
require "webhookdb/increase"
|
|
4
4
|
|
|
5
5
|
module Webhookdb::Replicator::IncreaseV1Mixin
|
|
6
|
-
def _mixin_backfill_url
|
|
7
|
-
raise NotImplementedError
|
|
8
|
-
end
|
|
9
|
-
|
|
10
6
|
def _webhook_response(request)
|
|
11
7
|
return Webhookdb::Increase.webhook_response(request, self.service_integration.webhook_secret)
|
|
12
8
|
end
|
|
13
9
|
|
|
10
|
+
def _resource_and_event(request) = request.body
|
|
11
|
+
|
|
14
12
|
def _timestamp_column_name
|
|
13
|
+
# We derive updated_at from the event, or use 'now'
|
|
15
14
|
return :updated_at
|
|
16
15
|
end
|
|
17
16
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
-
return body, nil
|
|
17
|
+
def _update_where_expr
|
|
18
|
+
ts = self._timestamp_column_name
|
|
19
|
+
return self.qualified_table_sequel_identifier[ts] < Sequel[:excluded][ts]
|
|
22
20
|
end
|
|
23
21
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
value = "https://api.increase.com" if field == "api_url" && value == ""
|
|
27
|
-
return super(field, value)
|
|
22
|
+
def on_dependency_webhook_upsert(_replicator, payload, **)
|
|
23
|
+
self.upsert_webhook_body(payload)
|
|
28
24
|
end
|
|
29
25
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#{self._backfill_command}
|
|
26
|
+
def _mixin_object_type = raise NotImplementedError
|
|
27
|
+
def _mixin_backfill_path = "/#{self._mixin_object_type}s"
|
|
28
|
+
def _mixin_backfill_url = "#{self._api_url}#{self._mixin_backfill_path}"
|
|
29
|
+
def _api_url = "https://api.increase.com"
|
|
30
|
+
|
|
31
|
+
def handle_event?(event) = event.fetch("associated_object_type") == self._mixin_object_type
|
|
32
|
+
|
|
33
|
+
def _fetch_enrichment(resource, _event, _request)
|
|
34
|
+
# If the resource type isn't what we expect, it must be an event.
|
|
35
|
+
# In that case, we need to fetch the resource from the API,
|
|
36
|
+
# and replace the event body in prepare_for_insert.
|
|
37
|
+
# The updated_at becomes the event's created_at,
|
|
38
|
+
# which should be fine- it's better than setting updated_at to 'now'
|
|
39
|
+
# since that will be confusing as it looks like a resource was recently updated.
|
|
40
|
+
rtype = resource.fetch("type")
|
|
41
|
+
return nil if rtype == self._mixin_object_type
|
|
42
|
+
raise Webhookdb::InvalidPrecondition, "unexpected resource: #{resource}" unless
|
|
43
|
+
rtype == "event" && resource.fetch("associated_object_type") == self._mixin_object_type
|
|
44
|
+
response = Webhookdb::Http.get(
|
|
45
|
+
self._mixin_backfill_url + "/#{resource.fetch('associated_object_id')}",
|
|
46
|
+
{},
|
|
47
|
+
headers: self._auth_headers,
|
|
48
|
+
logger: self.logger,
|
|
49
|
+
timeout: Webhookdb::Increase.http_timeout,
|
|
55
50
|
)
|
|
56
|
-
return
|
|
51
|
+
return response.parsed_response.merge("updated_at" => resource.fetch("created_at"))
|
|
57
52
|
end
|
|
58
53
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
From your Increase admin dashboard, go to Settings -> Development -> API Keys.
|
|
64
|
-
We'll need the Production key--copy that value to your clipboard.
|
|
65
|
-
)
|
|
66
|
-
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
unless self.service_integration.api_url.present?
|
|
70
|
-
step.output = %(Great. Now we want to make sure we're sending API requests to the right place.
|
|
71
|
-
For Increase, the API url is different when you are in Sandbox mode and when you are in Production mode.
|
|
72
|
-
For Sandbox mode, the API root url is:
|
|
73
|
-
|
|
74
|
-
https://sandbox.increase.com
|
|
75
|
-
|
|
76
|
-
For Production mode, which is our default, it is:
|
|
54
|
+
def _prepare_for_insert(resource, event, request, enrichment)
|
|
55
|
+
resource = enrichment if enrichment
|
|
56
|
+
return super(resource, event, request, nil)
|
|
57
|
+
end
|
|
77
58
|
|
|
78
|
-
|
|
59
|
+
def _app_sint = Webhookdb::Replicator.find_at_root!(self.service_integration, service_name: "increase_app_v1")
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
61
|
+
def _auth_headers
|
|
62
|
+
return {"Authorization" => ("Bearer " + self._app_sint.backfill_key)}
|
|
63
|
+
end
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
step.output =
|
|
88
|
-
|
|
65
|
+
def calculate_backfill_state_machine
|
|
66
|
+
if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
67
|
+
step.output = %(This replicator is managed automatically using OAuth through Increase.
|
|
68
|
+
Head over to #{Webhookdb::Replicator::IncreaseAppV1.descriptor.install_url} to learn more.)
|
|
69
|
+
return step
|
|
89
70
|
end
|
|
90
|
-
|
|
71
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
91
72
|
step.needs_input = false
|
|
92
73
|
step.output = %(Great! We are going to start backfilling your #{self.resource_name_plural}.
|
|
93
|
-
#{self._query_help_output}
|
|
94
|
-
)
|
|
74
|
+
#{self._query_help_output})
|
|
95
75
|
step.complete = true
|
|
96
76
|
return step
|
|
97
77
|
end
|
|
98
78
|
|
|
99
|
-
def _verify_backfill_401_err_msg
|
|
100
|
-
return "It looks like that API Key is invalid. Please reenter the API Key you just created:"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def _verify_backfill_err_msg
|
|
104
|
-
return "An error occurred. Please reenter the API Key you just created:"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
79
|
def _fetch_backfill_page(pagination_token, **_kwargs)
|
|
108
80
|
query = {}
|
|
109
81
|
(query[:cursor] = pagination_token) if pagination_token.present?
|
|
82
|
+
fetched_at = Time.now
|
|
110
83
|
response = Webhookdb::Http.get(
|
|
111
84
|
self._mixin_backfill_url,
|
|
112
85
|
query,
|
|
113
|
-
headers:
|
|
86
|
+
headers: self._auth_headers,
|
|
114
87
|
logger: self.logger,
|
|
115
88
|
timeout: Webhookdb::Increase.http_timeout,
|
|
116
89
|
)
|
|
117
90
|
data = response.parsed_response
|
|
118
91
|
next_page_param = data.dig("response_metadata", "next_cursor")
|
|
119
|
-
|
|
92
|
+
rows = data["data"]
|
|
93
|
+
# In general, we want to use webhooks/events to keep rows updated.
|
|
94
|
+
# But if we are backfilling, touch the 'updated at' timestamp to make sure
|
|
95
|
+
# these rows get inserted.
|
|
96
|
+
# It does mess up history, but we can't get that history to be accurate
|
|
97
|
+
# in the case of a backfill anyway.
|
|
98
|
+
rows.each { |r| r["updated_at"] = fetched_at }
|
|
99
|
+
return rows, next_page_param
|
|
120
100
|
end
|
|
121
101
|
end
|
|
@@ -11,10 +11,10 @@ class Webhookdb::Replicator::IncreaseWireTransferV1 < Webhookdb::Replicator::Bas
|
|
|
11
11
|
def self.descriptor
|
|
12
12
|
return Webhookdb::Replicator::Descriptor.new(
|
|
13
13
|
name: "increase_wire_transfer_v1",
|
|
14
|
-
ctor:
|
|
14
|
+
ctor: self,
|
|
15
15
|
feature_roles: [],
|
|
16
16
|
resource_name_singular: "Increase Wire Transfer",
|
|
17
|
-
|
|
17
|
+
dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
|
|
18
18
|
supports_backfill: true,
|
|
19
19
|
api_docs_url: "https://increase.com/documentation/api",
|
|
20
20
|
)
|
|
@@ -30,32 +30,13 @@ class Webhookdb::Replicator::IncreaseWireTransferV1 < Webhookdb::Replicator::Bas
|
|
|
30
30
|
Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
|
|
31
31
|
Webhookdb::Replicator::Column.new(:amount, INTEGER, index: true),
|
|
32
32
|
Webhookdb::Replicator::Column.new(:approved_at, TIMESTAMP, data_key: ["approval", "approved_at"]),
|
|
33
|
-
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP,
|
|
33
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
|
|
34
34
|
Webhookdb::Replicator::Column.new(:routing_number, TEXT, index: true),
|
|
35
35
|
Webhookdb::Replicator::Column.new(:status, TEXT),
|
|
36
|
-
Webhookdb::Replicator::Column.new(:template_id, TEXT),
|
|
37
36
|
Webhookdb::Replicator::Column.new(:transaction_id, TEXT, index: true),
|
|
38
|
-
Webhookdb::Replicator::Column.new(
|
|
39
|
-
:updated_at,
|
|
40
|
-
TIMESTAMP,
|
|
41
|
-
data_key: "created_at",
|
|
42
|
-
event_key: "created_at",
|
|
43
|
-
defaulter: :now,
|
|
44
|
-
optional: true,
|
|
45
|
-
index: true,
|
|
46
|
-
),
|
|
37
|
+
Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
|
|
47
38
|
]
|
|
48
39
|
end
|
|
49
40
|
|
|
50
|
-
def
|
|
51
|
-
return self._find_resource_and_event(request.body, "wire_transfer")
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def _update_where_expr
|
|
55
|
-
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def _mixin_backfill_url
|
|
59
|
-
return "#{self.service_integration.api_url}/wire_transfers"
|
|
60
|
-
end
|
|
41
|
+
def _mixin_object_type = "wire_transfer"
|
|
61
42
|
end
|
|
@@ -25,12 +25,59 @@ class Webhookdb::Replicator::IntercomContactV1 < Webhookdb::Replicator::Base
|
|
|
25
25
|
|
|
26
26
|
def _denormalized_columns
|
|
27
27
|
return [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Webhookdb::Replicator::Column.new(:
|
|
31
|
-
Webhookdb::Replicator::Column.new(:
|
|
28
|
+
# All of these fields are missing on delete.
|
|
29
|
+
# We merge the deleted info into an existing one when handling the upsert.
|
|
30
|
+
Webhookdb::Replicator::Column.new(:external_id, TEXT, optional: true, index: true),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:email, TEXT, optional: true, index: true),
|
|
32
|
+
Webhookdb::Replicator::Column.new(
|
|
33
|
+
:created_at, TIMESTAMP, converter: QUESTIONABLE_TIMESTAMP, optional: true, index: true,
|
|
34
|
+
),
|
|
35
|
+
Webhookdb::Replicator::Column.new(
|
|
36
|
+
:updated_at, TIMESTAMP, converter: QUESTIONABLE_TIMESTAMP, optional: true, index: true,
|
|
37
|
+
),
|
|
38
|
+
# This is set in the contact.deleted webhook
|
|
39
|
+
Webhookdb::Replicator::Column.new(:deleted_at, TIMESTAMP, optional: true),
|
|
40
|
+
# This is set in the contact.archived webhook
|
|
41
|
+
Webhookdb::Replicator::Column.new(:archived_at, TIMESTAMP, optional: true),
|
|
32
42
|
]
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def _mixin_backfill_url = "https://api.intercom.io/contacts"
|
|
46
|
+
|
|
47
|
+
def _resource_and_event(request)
|
|
48
|
+
resource, event = super
|
|
49
|
+
return resource, nil if event.nil?
|
|
50
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
|
51
|
+
case event.fetch("topic")
|
|
52
|
+
when "contact.deleted"
|
|
53
|
+
resource["updated_at"] = Time.now
|
|
54
|
+
resource["deleted_at"] = Time.now
|
|
55
|
+
when "contact.archived"
|
|
56
|
+
resource["updated_at"] = Time.now
|
|
57
|
+
resource["archived_at"] = Time.now
|
|
58
|
+
when "contact.unsubscribed"
|
|
59
|
+
resource = resource.fetch("contact")
|
|
60
|
+
end
|
|
61
|
+
return resource, event
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def _upsert_update_expr(inserting, enrichment: nil)
|
|
65
|
+
full_update = super
|
|
66
|
+
# In the case of a delete or archive, update the deleted_at/archived_at field,
|
|
67
|
+
# and merge 'deleted' or 'archived' into the :data field.
|
|
68
|
+
if inserting[:deleted_at]
|
|
69
|
+
status_key = :deleted_at
|
|
70
|
+
status_field = "deleted"
|
|
71
|
+
elsif inserting[:archived_at]
|
|
72
|
+
status_key = :archived_at
|
|
73
|
+
status_field = "archived"
|
|
74
|
+
else
|
|
75
|
+
return full_update
|
|
76
|
+
end
|
|
77
|
+
result = {updated_at: full_update.fetch(:updated_at)}
|
|
78
|
+
result[status_key] = full_update.fetch(status_key)
|
|
79
|
+
data_col = Sequel[self.service_integration.table_name.to_sym][:data]
|
|
80
|
+
result[:data] = Sequel.join([data_col, Sequel.lit("'{\"#{status_field}\":true}'::jsonb")])
|
|
81
|
+
return result
|
|
82
|
+
end
|
|
36
83
|
end
|
|
@@ -25,14 +25,50 @@ class Webhookdb::Replicator::IntercomConversationV1 < Webhookdb::Replicator::Bas
|
|
|
25
25
|
|
|
26
26
|
def _denormalized_columns
|
|
27
27
|
return [
|
|
28
|
-
Webhookdb::Replicator::Column.new(:title, TEXT),
|
|
29
|
-
Webhookdb::Replicator::Column.new(:state, TEXT),
|
|
30
|
-
Webhookdb::Replicator::Column.new(:open, BOOLEAN),
|
|
31
|
-
Webhookdb::Replicator::Column.new(:read, BOOLEAN),
|
|
32
|
-
Webhookdb::Replicator::Column.new(
|
|
33
|
-
|
|
28
|
+
Webhookdb::Replicator::Column.new(:title, TEXT, optional: true),
|
|
29
|
+
Webhookdb::Replicator::Column.new(:state, TEXT, optional: true),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:open, BOOLEAN, optional: true),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:read, BOOLEAN, optional: true),
|
|
32
|
+
Webhookdb::Replicator::Column.new(
|
|
33
|
+
:created_at, TIMESTAMP, converter: QUESTIONABLE_TIMESTAMP, optional: true, index: true,
|
|
34
|
+
),
|
|
35
|
+
Webhookdb::Replicator::Column.new(
|
|
36
|
+
:updated_at, TIMESTAMP, converter: QUESTIONABLE_TIMESTAMP, optional: true, index: true,
|
|
37
|
+
),
|
|
38
|
+
Webhookdb::Replicator::Column.new(:deleted_at, TIMESTAMP, optional: true, index: true),
|
|
34
39
|
]
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
def _mixin_backfill_url = "https://api.intercom.io/conversations"
|
|
43
|
+
|
|
44
|
+
def _resource_and_event(request)
|
|
45
|
+
resource, event = super
|
|
46
|
+
return resource, nil if event.nil?
|
|
47
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
|
48
|
+
case event.fetch("topic")
|
|
49
|
+
when "conversation.deleted"
|
|
50
|
+
resource["id"] = resource.fetch("conversation_id")
|
|
51
|
+
resource["updated_at"] = Time.now
|
|
52
|
+
resource["deleted_at"] = Time.now
|
|
53
|
+
when "conversation.contact.attached", "conversation.contact.detached"
|
|
54
|
+
# The convo is in resource['conversation']['model'], and doesn't have a number of fields.
|
|
55
|
+
# This doesn't seem like an important enough event to track for now,
|
|
56
|
+
# unless we start to do it relationally.
|
|
57
|
+
return nil, nil
|
|
58
|
+
end
|
|
59
|
+
return resource, event
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def _upsert_update_expr(inserting, enrichment: nil)
|
|
63
|
+
full_update = super
|
|
64
|
+
# In the case of a delete, update the deleted_at field and merge 'deleted' into the :data field.
|
|
65
|
+
return full_update unless inserting[:deleted_at]
|
|
66
|
+
data_col = Sequel[self.service_integration.table_name.to_sym][:data]
|
|
67
|
+
result = {
|
|
68
|
+
updated_at: full_update.fetch(:updated_at),
|
|
69
|
+
deleted_at: full_update.fetch(:deleted_at),
|
|
70
|
+
data: Sequel.join([data_col, Sequel.lit("'{\"deleted\":true}'::jsonb")]),
|
|
71
|
+
}
|
|
72
|
+
return result
|
|
73
|
+
end
|
|
38
74
|
end
|
|
@@ -25,17 +25,13 @@ class Webhookdb::Replicator::IntercomMarketplaceRootV1 < Webhookdb::Replicator::
|
|
|
25
25
|
return []
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def _upsert_webhook(**_kwargs)
|
|
29
|
-
raise NotImplementedError("This is a stub integration only for auth purposes.")
|
|
30
|
-
end
|
|
28
|
+
def _upsert_webhook(**_kwargs) = raise NotImplementedError("This is a stub integration only for auth purposes.")
|
|
31
29
|
|
|
32
30
|
def _fetch_backfill_page(*)
|
|
33
31
|
return [], nil
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
def webhook_response(_request)
|
|
37
|
-
raise NotImplementedError("This is a stub integration only for auth purposes.")
|
|
38
|
-
end
|
|
34
|
+
def webhook_response(_request) = raise NotImplementedError("This is a stub integration only for auth purposes.")
|
|
39
35
|
|
|
40
36
|
def calculate_backfill_state_machine
|
|
41
37
|
step = Webhookdb::Replicator::StateMachineStep.new
|
|
@@ -44,13 +40,6 @@ class Webhookdb::Replicator::IntercomMarketplaceRootV1 < Webhookdb::Replicator::
|
|
|
44
40
|
return step
|
|
45
41
|
end
|
|
46
42
|
|
|
47
|
-
def get_auth_headers
|
|
48
|
-
return {
|
|
49
|
-
"Authorization" => "Bearer #{self.service_integration.backfill_key}",
|
|
50
|
-
"Accept" => "application/json",
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
|
|
54
43
|
def build_dependents
|
|
55
44
|
org = self.service_integration.organization
|
|
56
45
|
contact_sint = Webhookdb::ServiceIntegration.create_disambiguated(
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webhookdb::Replicator::IntercomV1Mixin
|
|
4
|
-
#
|
|
4
|
+
# Timestamps can be unix timestamps when listing a resource,
|
|
5
|
+
# or strings in other cases, like webhooks. This may have to do with API versions.
|
|
6
|
+
# Handle both.
|
|
7
|
+
QUESTIONABLE_TIMESTAMP = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
8
|
+
ruby: lambda do |i, **_|
|
|
9
|
+
return nil if i.nil?
|
|
10
|
+
return Time.at(i)
|
|
11
|
+
rescue TypeError
|
|
12
|
+
return Time.parse(i)
|
|
13
|
+
end,
|
|
14
|
+
sql: lambda do |*|
|
|
15
|
+
# We would have to check the type of the data, which is a pain, so don't worry about this for now.
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end,
|
|
18
|
+
)
|
|
5
19
|
|
|
6
20
|
# Quick note on these Intercom integrations: although we will technically be bringing in information from webhooks,
|
|
7
21
|
# all webhooks for the WebhookDB app will use a single endpoint and we use the WebhookDB app's Client Secret for
|
|
@@ -34,20 +48,12 @@ module Webhookdb::Replicator::IntercomV1Mixin
|
|
|
34
48
|
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
35
49
|
end
|
|
36
50
|
|
|
37
|
-
def _timestamp_column_name
|
|
38
|
-
return :updated_at
|
|
39
|
-
end
|
|
51
|
+
def _timestamp_column_name = :updated_at
|
|
40
52
|
|
|
41
53
|
def _webhook_response(request)
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return Webhookdb::WebhookResponse.error("missing hmac") if intercom_auth.nil?
|
|
46
|
-
request.body.rewind
|
|
47
|
-
request_data = request.body.read
|
|
48
|
-
verified = Webhookdb::Intercom.verify_webhook(request_data, intercom_auth)
|
|
49
|
-
return Webhookdb::WebhookResponse.ok if verified
|
|
50
|
-
return Webhookdb::WebhookResponse.error("invalid hmac")
|
|
54
|
+
# Intercom webhooks are done through a centralized oauth replicator,
|
|
55
|
+
# so the secret is for the app, not the individual replicator.
|
|
56
|
+
return Webhookdb::Intercom.webhook_response(request, Webhookdb::Intercom.client_secret)
|
|
51
57
|
end
|
|
52
58
|
|
|
53
59
|
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
@@ -67,9 +73,7 @@ module Webhookdb::Replicator::IntercomV1Mixin
|
|
|
67
73
|
return
|
|
68
74
|
end
|
|
69
75
|
|
|
70
|
-
def _mixin_backfill_url
|
|
71
|
-
raise NotImplementedError
|
|
72
|
-
end
|
|
76
|
+
def _mixin_backfill_url = raise NotImplementedError
|
|
73
77
|
|
|
74
78
|
def _fetch_backfill_page(pagination_token, **_kwargs)
|
|
75
79
|
unless self.auth_credentials?
|
|
@@ -29,7 +29,7 @@ module Webhookdb::Replicator::OAuthRefreshAccessTokenMixin
|
|
|
29
29
|
if got
|
|
30
30
|
yield got
|
|
31
31
|
else
|
|
32
|
-
self.logger.
|
|
32
|
+
self.logger.debug "creating_access_token", access_token_cache_key: key
|
|
33
33
|
form_body = URI.encode_www_form(
|
|
34
34
|
{
|
|
35
35
|
client_id: self.service_integration.backfill_key,
|
|
@@ -117,7 +117,7 @@ module Webhookdb::Replicator::SponsyV1Mixin
|
|
|
117
117
|
self.find_api_key.blank?
|
|
118
118
|
|
|
119
119
|
publications_svc = self.service_integration.depends_on.replicator
|
|
120
|
-
backfillers = publications_svc.
|
|
120
|
+
backfillers = publications_svc.admin_dataset(timeout: :fast) do |pub_ds|
|
|
121
121
|
pub_ds = Webhookdb::Dbutil.reduce_expr(
|
|
122
122
|
pub_ds,
|
|
123
123
|
:|,
|
|
@@ -54,6 +54,9 @@ class Webhookdb::Replicator::TransistorEpisodeV1 < Webhookdb::Replicator::Base
|
|
|
54
54
|
index: true,
|
|
55
55
|
data_key: ["attributes", "updated_at"],
|
|
56
56
|
),
|
|
57
|
+
|
|
58
|
+
Webhookdb::Replicator::Column.new(:transcript_text, TEXT, optional: true),
|
|
59
|
+
|
|
57
60
|
# Ideally these would have converters, but they'd be very confusing, and when this was built
|
|
58
61
|
# we only had one transistor user, so we truncated the table instead.
|
|
59
62
|
Webhookdb::Replicator::Column.new(:api_format, INTEGER, optional: true),
|
|
@@ -93,6 +96,7 @@ class Webhookdb::Replicator::TransistorEpisodeV1 < Webhookdb::Replicator::Base
|
|
|
93
96
|
h[:logical_description] = description
|
|
94
97
|
h[:api_format] = 1
|
|
95
98
|
end
|
|
99
|
+
h.merge!(enrichment) if enrichment
|
|
96
100
|
return h
|
|
97
101
|
end
|
|
98
102
|
|
|
@@ -133,6 +137,19 @@ class Webhookdb::Replicator::TransistorEpisodeV1 < Webhookdb::Replicator::Base
|
|
|
133
137
|
|
|
134
138
|
BLOCK_ELEMENT_TAGS = ["p", "div"].freeze
|
|
135
139
|
|
|
140
|
+
def _fetch_enrichment(resource, *)
|
|
141
|
+
transcript_url = resource.fetch("attributes").fetch("transcript_url", nil)
|
|
142
|
+
return nil if transcript_url.blank?
|
|
143
|
+
(transcript_url += ".txt") unless transcript_url.end_with?(".txt")
|
|
144
|
+
resp = Webhookdb::Http.get(
|
|
145
|
+
transcript_url,
|
|
146
|
+
logger: self.logger,
|
|
147
|
+
timeout: Webhookdb::Transistor.http_timeout,
|
|
148
|
+
)
|
|
149
|
+
transcript_text = resp.body
|
|
150
|
+
return {transcript_text:}
|
|
151
|
+
end
|
|
152
|
+
|
|
136
153
|
def upsert_has_deps?
|
|
137
154
|
return true
|
|
138
155
|
end
|