webhookdb 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/040_saved_query_fix_unique.rb +17 -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 +10 -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 +17 -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 +58 -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
|