webhookdb 1.3.1 → 1.5.0
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 → index-9306dd28.js} +39 -39
- data/admin-dist/index.html +1 -1
- data/data/messages/templates/errors/generic_backfill.email.liquid +30 -0
- data/data/messages/templates/errors/icalendar_fetch.email.liquid +8 -2
- data/data/messages/templates/specs/with_fields.email.liquid +6 -0
- data/db/migrations/026_undo_integration_backfill_cursor.rb +2 -0
- data/db/migrations/032_remove_db_defaults.rb +2 -0
- data/db/migrations/043_text_search.rb +2 -0
- data/db/migrations/045_system_log.rb +15 -0
- data/db/migrations/046_indices.rb +14 -0
- data/db/migrations/047_sync_parallelism.rb +9 -0
- data/db/migrations/048_sync_stats.rb +9 -0
- data/db/migrations/049_error_handlers.rb +18 -0
- data/db/migrations/050_logged_webhook_indices.rb +25 -0
- data/db/migrations/051_partitioning.rb +9 -0
- data/integration/async_spec.rb +0 -2
- data/integration/service_integrations_spec.rb +0 -2
- data/lib/amigo/durable_job.rb +2 -2
- data/lib/amigo/job_in_context.rb +12 -0
- data/lib/webhookdb/admin.rb +6 -0
- data/lib/webhookdb/admin_api/data_provider.rb +1 -0
- data/lib/webhookdb/admin_api/entities.rb +8 -0
- data/lib/webhookdb/aggregate_result.rb +1 -1
- data/lib/webhookdb/api/entities.rb +6 -2
- data/lib/webhookdb/api/error_handlers.rb +104 -0
- data/lib/webhookdb/api/helpers.rb +25 -1
- data/lib/webhookdb/api/icalproxy.rb +22 -0
- data/lib/webhookdb/api/install.rb +2 -1
- data/lib/webhookdb/api/organizations.rb +6 -0
- data/lib/webhookdb/api/saved_queries.rb +1 -0
- data/lib/webhookdb/api/saved_views.rb +1 -0
- data/lib/webhookdb/api/service_integrations.rb +2 -1
- data/lib/webhookdb/api/sync_targets.rb +1 -1
- data/lib/webhookdb/api/system.rb +5 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +1 -0
- data/lib/webhookdb/api.rb +4 -1
- data/lib/webhookdb/apps.rb +4 -0
- data/lib/webhookdb/async/autoscaler.rb +10 -0
- data/lib/webhookdb/async/job.rb +4 -0
- data/lib/webhookdb/async/scheduled_job.rb +4 -0
- data/lib/webhookdb/async.rb +2 -0
- data/lib/webhookdb/backfiller.rb +17 -4
- data/lib/webhookdb/concurrent.rb +96 -0
- data/lib/webhookdb/connection_cache.rb +57 -10
- data/lib/webhookdb/console.rb +1 -1
- data/lib/webhookdb/customer/reset_code.rb +1 -1
- data/lib/webhookdb/customer.rb +5 -4
- data/lib/webhookdb/database_document.rb +1 -1
- data/lib/webhookdb/db_adapter/default_sql.rb +1 -14
- data/lib/webhookdb/db_adapter/partition.rb +14 -0
- data/lib/webhookdb/db_adapter/partitioning.rb +8 -0
- data/lib/webhookdb/db_adapter/pg.rb +77 -5
- data/lib/webhookdb/db_adapter/snowflake.rb +15 -6
- data/lib/webhookdb/db_adapter.rb +25 -3
- data/lib/webhookdb/dbutil.rb +2 -0
- data/lib/webhookdb/errors.rb +34 -0
- data/lib/webhookdb/fixtures/logged_webhooks.rb +4 -0
- data/lib/webhookdb/fixtures/organization_error_handlers.rb +20 -0
- data/lib/webhookdb/http.rb +30 -16
- data/lib/webhookdb/icalendar.rb +30 -9
- data/lib/webhookdb/jobs/amigo_test_jobs.rb +1 -1
- data/lib/webhookdb/jobs/backfill.rb +21 -25
- data/lib/webhookdb/jobs/create_mirror_table.rb +3 -4
- data/lib/webhookdb/jobs/deprecated_jobs.rb +3 -0
- data/lib/webhookdb/jobs/emailer.rb +2 -1
- data/lib/webhookdb/jobs/front_signalwire_message_channel_sync_inbound.rb +15 -0
- data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +7 -2
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +74 -11
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs_for_urls.rb +22 -0
- data/lib/webhookdb/jobs/icalendar_sync.rb +21 -9
- data/lib/webhookdb/jobs/increase_event_handler.rb +3 -2
- data/lib/webhookdb/jobs/{logged_webhook_replay.rb → logged_webhooks_replay.rb} +5 -3
- data/lib/webhookdb/jobs/message_dispatched.rb +1 -0
- data/lib/webhookdb/jobs/model_event_system_log_tracker.rb +112 -0
- data/lib/webhookdb/jobs/monitor_metrics.rb +29 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify.rb +32 -0
- data/lib/webhookdb/jobs/organization_database_migration_run.rb +4 -6
- data/lib/webhookdb/jobs/organization_error_handler_dispatch.rb +26 -0
- data/lib/webhookdb/jobs/prepare_database_connections.rb +1 -0
- data/lib/webhookdb/jobs/process_webhook.rb +11 -12
- data/lib/webhookdb/jobs/renew_watch_channel.rb +10 -10
- data/lib/webhookdb/jobs/replication_migration.rb +5 -2
- data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +1 -2
- data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -2
- data/lib/webhookdb/jobs/send_invite.rb +3 -2
- data/lib/webhookdb/jobs/send_test_webhook.rb +1 -3
- data/lib/webhookdb/jobs/send_webhook.rb +4 -5
- data/lib/webhookdb/jobs/stale_row_deleter.rb +31 -0
- data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +3 -0
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +9 -15
- data/lib/webhookdb/jobs/{webhook_subscription_delivery_attempt.rb → webhook_subscription_delivery_event.rb} +5 -8
- data/lib/webhookdb/liquid/expose.rb +1 -1
- data/lib/webhookdb/liquid/filters.rb +1 -1
- data/lib/webhookdb/liquid/partial.rb +2 -2
- data/lib/webhookdb/logged_webhook/resilient.rb +3 -3
- data/lib/webhookdb/logged_webhook.rb +16 -2
- data/lib/webhookdb/message/email_transport.rb +1 -1
- data/lib/webhookdb/message/transport.rb +1 -1
- data/lib/webhookdb/message.rb +55 -4
- data/lib/webhookdb/messages/error_generic_backfill.rb +47 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +5 -0
- data/lib/webhookdb/messages/error_signalwire_send_sms.rb +2 -0
- data/lib/webhookdb/messages/specs.rb +16 -0
- data/lib/webhookdb/organization/alerting.rb +56 -6
- data/lib/webhookdb/organization/database_migration.rb +2 -2
- data/lib/webhookdb/organization/db_builder.rb +5 -4
- data/lib/webhookdb/organization/error_handler.rb +141 -0
- data/lib/webhookdb/organization.rb +76 -10
- data/lib/webhookdb/postgres/model.rb +1 -0
- data/lib/webhookdb/postgres/model_utilities.rb +2 -0
- data/lib/webhookdb/postgres.rb +3 -4
- data/lib/webhookdb/replicator/base.rb +202 -68
- data/lib/webhookdb/replicator/base_stale_row_deleter.rb +165 -0
- data/lib/webhookdb/replicator/column.rb +2 -0
- data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +0 -1
- data/lib/webhookdb/replicator/fake.rb +106 -88
- data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +131 -61
- data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +17 -0
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +197 -32
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +20 -44
- data/lib/webhookdb/replicator/icalendar_event_v1_partitioned.rb +33 -0
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +1 -0
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +1 -0
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +49 -6
- data/lib/webhookdb/replicator/partitionable_mixin.rb +116 -0
- data/lib/webhookdb/replicator/shopify_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/signalwire_message_v1.rb +31 -1
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +0 -1
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +11 -5
- data/lib/webhookdb/replicator/webhook_request.rb +8 -0
- data/lib/webhookdb/replicator.rb +6 -3
- data/lib/webhookdb/service/helpers.rb +4 -0
- data/lib/webhookdb/service/middleware.rb +6 -2
- data/lib/webhookdb/service/view_api.rb +1 -1
- data/lib/webhookdb/service.rb +10 -10
- data/lib/webhookdb/service_integration.rb +19 -1
- data/lib/webhookdb/signalwire.rb +1 -1
- data/lib/webhookdb/spec_helpers/async.rb +0 -4
- data/lib/webhookdb/spec_helpers/sentry.rb +32 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +239 -64
- data/lib/webhookdb/spec_helpers.rb +1 -0
- data/lib/webhookdb/sync_target.rb +202 -34
- data/lib/webhookdb/system_log_event.rb +9 -0
- data/lib/webhookdb/tasks/admin.rb +1 -1
- data/lib/webhookdb/tasks/annotate.rb +1 -1
- data/lib/webhookdb/tasks/db.rb +13 -1
- data/lib/webhookdb/tasks/docs.rb +1 -1
- data/lib/webhookdb/tasks/fixture.rb +1 -1
- data/lib/webhookdb/tasks/message.rb +1 -1
- data/lib/webhookdb/tasks/regress.rb +1 -1
- data/lib/webhookdb/tasks/release.rb +1 -1
- data/lib/webhookdb/tasks/sidekiq.rb +1 -1
- data/lib/webhookdb/tasks/specs.rb +1 -1
- data/lib/webhookdb/version.rb +1 -1
- data/lib/webhookdb/webhook_subscription.rb +3 -4
- data/lib/webhookdb.rb +34 -8
- metadata +114 -64
- data/lib/webhookdb/jobs/customer_created_notify_internal.rb +0 -22
- data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +0 -21
- data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +0 -21
- /data/lib/webhookdb/jobs/{logged_webhook_resilient_replay.rb → logged_webhooks_resilient_replay.rb} +0 -0
- /data/lib/webhookdb/jobs/{webhook_resource_notify_integrations.rb → webhookdb_resource_notify_integrations.rb} +0 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Mixin for replicators that support partitioning.
|
4
|
+
# Partitioning is currently in beta,
|
5
|
+
# with the following limitations/context:
|
6
|
+
#
|
7
|
+
# - They cannot be created from the CLI.
|
8
|
+
# Because the partitions must be created during the CREATE TABLE call,
|
9
|
+
# the partition_value must be set immediately on creation,
|
10
|
+
# or CREATE TABLE must be deferred.
|
11
|
+
# - CLI support would also require making sure this field isn't edited.
|
12
|
+
# This is an annoying change, so we're putting it off for now.
|
13
|
+
# - Instead, partitioned replicators must be created in the console.
|
14
|
+
# - The number of HASH partitions cannot be changed;
|
15
|
+
# there is no good way to handle this in Postgres so we don't bother here.
|
16
|
+
# - RANGE partitions are not supported.
|
17
|
+
# We need to support creating the partition when the INSERT fails.
|
18
|
+
# But creating the partitioned table definition itself does work/has a shared behavior at least.
|
19
|
+
# - Existing replicators cannot be converted to partitioned.
|
20
|
+
# This is theoretically possible, but it seems easier to just start over
|
21
|
+
# with a new replicator.
|
22
|
+
# - Instead:
|
23
|
+
# - If this is a 'child' replicator, then create a new parent and this child,
|
24
|
+
# then copy over the parent data, either directly (for icalendar)
|
25
|
+
# or using HTTP requests (like with Plaid or Google) where more logic is required.
|
26
|
+
# - Otherwise, it'll depend on the replicator.
|
27
|
+
# - Then to switch clients using the old replicator, to the new replicator, you can:
|
28
|
+
# - Then turn off all workers.
|
29
|
+
# - Rename the new table to the old, and old table to the new.
|
30
|
+
# - Update the service integrations, so the old one points to the new table name and opaque id,
|
31
|
+
# and the new one points to the old table name and opaque id.
|
32
|
+
#
|
33
|
+
module Webhookdb::Replicator::PartitionableMixin
|
34
|
+
# The partition method, like Webhookdb::DBAdapter::Partitioning::HASH
|
35
|
+
def partition_method = raise NotImplementedError
|
36
|
+
# The partition column name.
|
37
|
+
# Must be present in +_denormalized_columns+.
|
38
|
+
# @return [Symbol]
|
39
|
+
def partition_column_name = raise NotImplementedError
|
40
|
+
# The value for the denormalized column. For HASH partitioning this would be an integer,
|
41
|
+
# for RANGE partitioning this could be a timestamp, etc.
|
42
|
+
# Takes the resource and returns the value.
|
43
|
+
def partition_value(_resource) = raise NotImplementedError
|
44
|
+
|
45
|
+
def partition? = true
|
46
|
+
|
47
|
+
def partitioning
|
48
|
+
return Webhookdb::DBAdapter::Partitioning.new(by: self.partition_method, column: self.partition_column_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def _prepare_for_insert(resource, event, request, enrichment)
|
52
|
+
h = super
|
53
|
+
h[self.partition_column_name] = self.partition_value(resource)
|
54
|
+
return h
|
55
|
+
end
|
56
|
+
|
57
|
+
def _upsert_conflict_target
|
58
|
+
return [self.partition_column_name, self._remote_key_column.name]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convert the given string into a stable MD5-derived hash
|
62
|
+
# that can be stored in a (signed, 4 bit) INTEGER column.
|
63
|
+
def _str2inthash(s)
|
64
|
+
# MD5 is 128 bits/16 bytes/32 hex chars (2 chars per byte).
|
65
|
+
# Integers are 32 bits/4 bytes/8 hex chars.
|
66
|
+
# Grab the first 8 chars and convert it to an integer.
|
67
|
+
unsigned_md5int = Digest::MD5.hexdigest(s)[..8].to_i(16)
|
68
|
+
# Then AND it with a 32 bit bitmask to make sure it fits in 32 bits
|
69
|
+
# (though I'm not entirely sure why the above doesn't result in 32 bits always).
|
70
|
+
unsigned_int32 = unsigned_md5int & 0xFFFFFFFF
|
71
|
+
# Convert it from unsigned (0 to 4.2B) to signed (-2.1B to 2.1B) by subtracting 2.1B
|
72
|
+
# (the max 2 byte integer), as opposed to a 4 byte integer which we're dealing with here.
|
73
|
+
signed_md5int = unsigned_int32 - MAX_16BIT_INT
|
74
|
+
return signed_md5int
|
75
|
+
end
|
76
|
+
|
77
|
+
MAX_16BIT_INT = 2**31
|
78
|
+
|
79
|
+
# Return the partitions belonging to the table.
|
80
|
+
# @param db The organization connection.
|
81
|
+
# @return [Array<Webhookdb::DBAdapter::Partition>]
|
82
|
+
def existing_partitions(db)
|
83
|
+
# SELECT inhrelid::regclass AS child
|
84
|
+
# FROM pg_catalog.pg_inherits
|
85
|
+
# WHERE inhparent = 'my_schema.foo'::regclass;
|
86
|
+
parent = self.schema_and_table_symbols.map(&:to_s).join(".")
|
87
|
+
partnames = db[Sequel[:pg_catalog][:pg_inherits]].
|
88
|
+
where(inhparent: Sequel[parent].cast(:regclass)).
|
89
|
+
select_map(Sequel[:inhrelid].cast(:regclass))
|
90
|
+
parent_table = self.dbadapter_table
|
91
|
+
result = partnames.map do |part|
|
92
|
+
suffix = self.partition_suffix(part)
|
93
|
+
Webhookdb::DBAdapter::Partition.new(parent_table:, partition_name: part.to_sym, suffix:)
|
94
|
+
end
|
95
|
+
return result
|
96
|
+
end
|
97
|
+
|
98
|
+
def partition_suffix(partname)
|
99
|
+
return partname[/_[a-zA-Z\d]+$/].to_sym
|
100
|
+
end
|
101
|
+
|
102
|
+
def partition_align_name
|
103
|
+
tblname = self.service_integration.table_name
|
104
|
+
self.service_integration.organization.admin_connection do |db|
|
105
|
+
partitions = self.existing_partitions(db)
|
106
|
+
db.transaction do
|
107
|
+
partitions.each do |partition|
|
108
|
+
next if partition.partition_name.to_s.start_with?(tblname)
|
109
|
+
schema = partition.parent_table.schema.name
|
110
|
+
new_partname = "#{tblname}#{partition.suffix}"
|
111
|
+
db << "ALTER TABLE #{schema}.#{partition.partition_name} RENAME TO #{new_partname}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "webhookdb/errors"
|
3
4
|
require "webhookdb/signalwire"
|
5
|
+
require "webhookdb/messages/error_generic_backfill"
|
4
6
|
|
5
7
|
class Webhookdb::Replicator::SignalwireMessageV1 < Webhookdb::Replicator::Base
|
6
8
|
include Appydays::Loggable
|
@@ -50,7 +52,7 @@ class Webhookdb::Replicator::SignalwireMessageV1 < Webhookdb::Replicator::Base
|
|
50
52
|
h = u.host.gsub(/\.signalwire\.com$/, "")
|
51
53
|
value = h
|
52
54
|
end
|
53
|
-
return super
|
55
|
+
return super
|
54
56
|
end
|
55
57
|
|
56
58
|
def calculate_backfill_state_machine
|
@@ -180,4 +182,32 @@ Press 'Show' next to the newly-created API token, and copy it.)
|
|
180
182
|
|
181
183
|
return messages, data["next_page_uri"]
|
182
184
|
end
|
185
|
+
|
186
|
+
def on_backfill_error(be)
|
187
|
+
e = Webhookdb::Errors.find_cause(be) do |ex|
|
188
|
+
next true if ex.is_a?(Webhookdb::Http::Error) && ex.status == 401
|
189
|
+
next true if ex.is_a?(::SocketError)
|
190
|
+
end
|
191
|
+
return unless e
|
192
|
+
if e.is_a?(::SocketError)
|
193
|
+
response_status = 0
|
194
|
+
response_body = e.message
|
195
|
+
request_url = "<unknown>"
|
196
|
+
request_method = "<unknown>"
|
197
|
+
else
|
198
|
+
response_status = e.status
|
199
|
+
response_body = e.body
|
200
|
+
request_url = e.uri.to_s
|
201
|
+
request_method = e.http_method
|
202
|
+
end
|
203
|
+
message = Webhookdb::Messages::ErrorGenericBackfill.new(
|
204
|
+
self.service_integration,
|
205
|
+
response_status:,
|
206
|
+
response_body:,
|
207
|
+
request_url:,
|
208
|
+
request_method:,
|
209
|
+
)
|
210
|
+
self.service_integration.organization.alerting.dispatch_alert(message)
|
211
|
+
return true
|
212
|
+
end
|
183
213
|
end
|
@@ -141,11 +141,17 @@ class Webhookdb::Replicator::TransistorEpisodeV1 < Webhookdb::Replicator::Base
|
|
141
141
|
transcript_url = resource.fetch("attributes").fetch("transcript_url", nil)
|
142
142
|
return nil if transcript_url.blank?
|
143
143
|
(transcript_url += ".txt") unless transcript_url.end_with?(".txt")
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
144
|
+
begin
|
145
|
+
resp = Webhookdb::Http.get(
|
146
|
+
transcript_url,
|
147
|
+
logger: self.logger,
|
148
|
+
timeout: Webhookdb::Transistor.http_timeout,
|
149
|
+
)
|
150
|
+
rescue Webhookdb::Http::Error => e
|
151
|
+
# Not sure why this happens, but nothing we can do if it does.
|
152
|
+
return nil if e.status == 404
|
153
|
+
raise e
|
154
|
+
end
|
149
155
|
transcript_text = resp.body
|
150
156
|
return {transcript_text:}
|
151
157
|
end
|
@@ -6,4 +6,12 @@ class Webhookdb::Replicator::WebhookRequest < Webhookdb::TypedStruct
|
|
6
6
|
# When a webhook is processed synchronously, this will be set to the Rack::Request.
|
7
7
|
# Normal (async) webhook processing does not have this available.
|
8
8
|
attr_accessor :rack_request
|
9
|
+
|
10
|
+
JSON_KEYS = ["body", "headers", "path", "method"].freeze
|
11
|
+
def as_json
|
12
|
+
return JSON_KEYS.each_with_object({}) do |k, h|
|
13
|
+
v = self.send(k)
|
14
|
+
h[k] = v.as_json unless v.nil?
|
15
|
+
end
|
16
|
+
end
|
9
17
|
end
|
data/lib/webhookdb/replicator.rb
CHANGED
@@ -15,11 +15,14 @@ class Webhookdb::Replicator
|
|
15
15
|
PLUGIN_DIR = Pathname(__FILE__).dirname + PLUGIN_DIRNAME
|
16
16
|
|
17
17
|
# Raised when there is no service registered for a name.
|
18
|
-
class Invalid <
|
18
|
+
class Invalid < Webhookdb::WebhookdbError; end
|
19
19
|
|
20
20
|
# Raised when credentials to interact with a service are not set up.
|
21
21
|
# Usually this is due to a missing dependency.
|
22
|
-
class CredentialsMissing <
|
22
|
+
class CredentialsMissing < Webhookdb::WebhookdbError; end
|
23
|
+
|
24
|
+
# Raised when the columns or indices for a replicator are invalid.
|
25
|
+
class BrokenSpecification < Webhookdb::WebhookdbError; end
|
23
26
|
|
24
27
|
# Statically describe a replicator.
|
25
28
|
class Descriptor < Webhookdb::TypedStruct
|
@@ -142,7 +145,7 @@ class Webhookdb::Replicator
|
|
142
145
|
end
|
143
146
|
|
144
147
|
class IndexSpec < Webhookdb::TypedStruct
|
145
|
-
attr_reader :columns, :where
|
148
|
+
attr_reader :columns, :where, :identifier
|
146
149
|
end
|
147
150
|
|
148
151
|
class << self
|
@@ -16,6 +16,10 @@ module Webhookdb::Service::Helpers
|
|
16
16
|
return Webhookdb::Service.logger
|
17
17
|
end
|
18
18
|
|
19
|
+
def set_request_tags(tags)
|
20
|
+
Webhookdb::Service::Middleware::RequestLogger.set_request_tags(tags)
|
21
|
+
end
|
22
|
+
|
19
23
|
# Return the currently-authenticated user,
|
20
24
|
# or respond with a 401 if there is no authenticated user.
|
21
25
|
def current_customer
|
@@ -128,12 +128,16 @@ module Webhookdb::Service::Middleware
|
|
128
128
|
def request_tags(env)
|
129
129
|
tags = super
|
130
130
|
begin
|
131
|
-
|
131
|
+
c = env["warden"].user(:customer)
|
132
132
|
rescue Sequel::DatabaseError
|
133
133
|
# If we cant hit the database, ignore this for now.
|
134
134
|
# We run this code on all code paths, including those that don't need the customer,
|
135
135
|
# and we want those to run even if the DB is down (like health checks, for example).
|
136
|
-
nil
|
136
|
+
c = nil
|
137
|
+
end
|
138
|
+
if c
|
139
|
+
tags[:customer_id] = c.id || 0
|
140
|
+
tags[:customer] = c.email
|
137
141
|
end
|
138
142
|
return tags
|
139
143
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# Mixin for Grape API endpoints that use HTML rendering.
|
4
4
|
# This isn't tested well enough.
|
5
5
|
module Webhookdb::Service::ViewApi
|
6
|
-
class FormError <
|
6
|
+
class FormError < Webhookdb::WebhookdbError
|
7
7
|
attr_reader :status
|
8
8
|
|
9
9
|
def initialize(msg, status=400)
|
data/lib/webhookdb/service.rb
CHANGED
@@ -163,6 +163,10 @@ class Webhookdb::Service < Grape::API
|
|
163
163
|
error!(e.message, 405)
|
164
164
|
end
|
165
165
|
|
166
|
+
rescue_from Webhookdb::ExceptionCarrier do |e|
|
167
|
+
merror!(e.status, e.message, code: e.code, more: e.more, headers: e.headers)
|
168
|
+
end
|
169
|
+
|
166
170
|
rescue_from Webhookdb::LockFailed do |_e|
|
167
171
|
merror!(
|
168
172
|
409,
|
@@ -191,17 +195,7 @@ class Webhookdb::Service < Grape::API
|
|
191
195
|
status = e.respond_to?(:status) ? e.status : 500
|
192
196
|
error_id = SecureRandom.uuid
|
193
197
|
error_signature = Digest::MD5.hexdigest("#{e.class}: #{e.message}")
|
194
|
-
|
195
|
-
Webhookdb::Service.logger.error "[%s] Uncaught %p in service: %s" %
|
196
|
-
[error_id, e.class, e.message]
|
197
|
-
Webhookdb::Service.logger.debug { e.backtrace.join("\n") }
|
198
|
-
if ENV["PRINT_API_ERROR"]
|
199
|
-
puts e
|
200
|
-
puts e.backtrace
|
201
|
-
end
|
202
|
-
|
203
198
|
more = {error_id:, error_signature:}
|
204
|
-
|
205
199
|
if Webhookdb::Service.devmode
|
206
200
|
msg = e.message
|
207
201
|
more[:backtrace] = e.backtrace.join("\n")
|
@@ -210,6 +204,12 @@ class Webhookdb::Service < Grape::API
|
|
210
204
|
msg = "An internal error occurred of type #{error_signature}. Error ID: #{error_id}"
|
211
205
|
end
|
212
206
|
Webhookdb::Service.logger.error("api_exception", {error_id:, error_signature:}, e)
|
207
|
+
Webhookdb::Service.logger.debug { e.backtrace.join("\n") }
|
208
|
+
if ENV["PRINT_API_ERROR"]
|
209
|
+
puts e
|
210
|
+
puts e.backtrace
|
211
|
+
end
|
212
|
+
|
213
213
|
merror!(status, msg, code: "api_error", more:)
|
214
214
|
end
|
215
215
|
|
@@ -6,6 +6,8 @@ require "webhookdb/postgres/model"
|
|
6
6
|
require "sequel/plugins/soft_deletes"
|
7
7
|
|
8
8
|
class Webhookdb::ServiceIntegration < Webhookdb::Postgres::Model(:service_integrations)
|
9
|
+
include Webhookdb::Admin::Linked
|
10
|
+
|
9
11
|
class TableRenameError < Webhookdb::InvalidInput; end
|
10
12
|
|
11
13
|
# We limit the information that a user can access through the CLI to these fields.
|
@@ -70,7 +72,7 @@ class Webhookdb::ServiceIntegration < Webhookdb::Postgres::Model(:service_integr
|
|
70
72
|
end)
|
71
73
|
|
72
74
|
many_to_one :depends_on, class: self
|
73
|
-
one_to_many :dependents, key: :depends_on_id, class: self
|
75
|
+
one_to_many :dependents, key: :depends_on_id, class: self, order: :id
|
74
76
|
one_to_many :sync_targets, class: "Webhookdb::SyncTarget"
|
75
77
|
|
76
78
|
# @return [Webhookdb::ServiceIntegration]
|
@@ -139,10 +141,21 @@ class Webhookdb::ServiceIntegration < Webhookdb::Postgres::Model(:service_integr
|
|
139
141
|
select { |si| si.service_name == dep_descr.name }
|
140
142
|
end
|
141
143
|
|
144
|
+
# Return all dependents (integrations that depend on this one), breadth-first
|
145
|
+
# (that is, all children before grandchildren).
|
146
|
+
# @return [Array<Webhookdb::ServiceIntegration>]
|
142
147
|
def recursive_dependents
|
143
148
|
return self.dependents + self.dependents.flat_map(&:recursive_dependents)
|
144
149
|
end
|
145
150
|
|
151
|
+
# Return all service integrations this one depends on,
|
152
|
+
# in closest-ancestor order (that is, parent before grandparent).
|
153
|
+
# @return [Array<Webhookdb::ServiceIntegration>]
|
154
|
+
def recursive_dependencies
|
155
|
+
return [] if self.depends_on.nil?
|
156
|
+
return [self.depends_on].concat(self.depends_on.recursive_dependencies)
|
157
|
+
end
|
158
|
+
|
146
159
|
def destroy_self_and_all_dependents
|
147
160
|
self.dependents.each(&:destroy_self_and_all_dependents)
|
148
161
|
|
@@ -329,6 +342,11 @@ class Webhookdb::ServiceIntegration < Webhookdb::Postgres::Model(:service_integr
|
|
329
342
|
# @!attribute skip_webhook_verification
|
330
343
|
# @return [Boolean] Set this to disable webhook verification on this integration.
|
331
344
|
# Useful when replaying logged webhooks.
|
345
|
+
|
346
|
+
# @!attribute partition_value
|
347
|
+
# @return [Integer] Value to control partitioning. For replicators that use hash partitioning,
|
348
|
+
# this defines the number of partitions. For other partition types, like range,
|
349
|
+
# the meaning of this value depends on the replicator itself.
|
332
350
|
end
|
333
351
|
|
334
352
|
# Table: service_integrations
|
data/lib/webhookdb/signalwire.rb
CHANGED
@@ -9,7 +9,7 @@ module Webhookdb::Signalwire
|
|
9
9
|
|
10
10
|
configurable(:signalwire) do
|
11
11
|
setting :http_timeout, 30
|
12
|
-
setting :sms_allowlist, [], convert:
|
12
|
+
setting :sms_allowlist, [], convert: lambda(&:split)
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.send_sms(from:, to:, body:, project_id:, **kw)
|
@@ -22,10 +22,6 @@ module Webhookdb::SpecHelpers::Async
|
|
22
22
|
Webhookdb::Slack.http_client = Webhookdb::Slack::NoOpHttpClient.new
|
23
23
|
Webhookdb::Slack.suppress_all = false
|
24
24
|
end
|
25
|
-
if example.metadata[:sentry]
|
26
|
-
Webhookdb::Sentry.dsn = "http://public:secret@not-really-sentry.nope/someproject"
|
27
|
-
Webhookdb::Sentry.run_after_configured_hooks
|
28
|
-
end
|
29
25
|
end
|
30
26
|
|
31
27
|
context.after(:each) do |example|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/spec_helpers"
|
4
|
+
require "webhookdb/sentry"
|
5
|
+
|
6
|
+
module Webhookdb::SpecHelpers::Sentry
|
7
|
+
def self.included(context)
|
8
|
+
context.before(:each) do |example|
|
9
|
+
if example.metadata[:sentry]
|
10
|
+
# We need to fake doing what Sentry would be doing for initialization,
|
11
|
+
# so we can assert it has the right data in its scope.
|
12
|
+
Webhookdb::Sentry.dsn = "https://public:secret@test-sentry.webhookdb.com/whdb"
|
13
|
+
hub = Sentry::Hub.new(
|
14
|
+
Sentry::Client.new(Sentry::Configuration.new),
|
15
|
+
Sentry::Scope.new,
|
16
|
+
)
|
17
|
+
expect(Sentry).to_not be_initialized
|
18
|
+
Sentry.instance_variable_set(:@main_hub, hub)
|
19
|
+
expect(Sentry).to be_initialized
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context.after(:each) do |example|
|
24
|
+
if example.metadata[:sentry]
|
25
|
+
Webhookdb::Sentry.reset_configuration
|
26
|
+
expect(Sentry).to_not be_initialized
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|