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
data/admin-dist/index.html
CHANGED
@@ -115,7 +115,7 @@
|
|
115
115
|
</style>
|
116
116
|
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
117
117
|
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;500;600;700&display=swap" rel="stylesheet">
|
118
|
-
<script type="module" crossorigin src="/admin/assets/index-
|
118
|
+
<script type="module" crossorigin src="/admin/assets/index-9306dd28.js"></script>
|
119
119
|
</head>
|
120
120
|
|
121
121
|
<body>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{% expose subject %}WebhookDB: {{ friendly_name }} Error{% endexpose %}
|
2
|
+
|
3
|
+
{% partial 'greeting' %}
|
4
|
+
|
5
|
+
<p>
|
6
|
+
WebhookDB encountered an error trying to sync {{ friendly_name }} data using your configured credentials. We have the following information about the failure:
|
7
|
+
</p>
|
8
|
+
<ul>
|
9
|
+
<li>Service Integration Name: {{ service_name }}, ID: <code>{{ opaque_id }}</code></li>
|
10
|
+
<li>Request: <code>{{ request_method }} {{ request_url }}</code></li>
|
11
|
+
<li>Response Status: <code>{{ response_status }}</code></li>
|
12
|
+
<li>Body: <code>{{ response_body }}</code></li>
|
13
|
+
</ul>
|
14
|
+
<p>
|
15
|
+
Usually this indicates an API key has been revoked or some other misconfiguration, rather than being an error in WebhookDB itself.
|
16
|
+
</p>
|
17
|
+
<p>To fix this integration, head over to <a href="{{ app_url }}">{{ app_url }}</a>, log in, and run:</p>
|
18
|
+
<pre>
|
19
|
+
webhookdb integrations reset {{ opaque_id }}
|
20
|
+
</pre>
|
21
|
+
<p>
|
22
|
+
If you no longer wish to use this integration, it can be deleted using:
|
23
|
+
</p>
|
24
|
+
<pre>
|
25
|
+
webhookdb integrations delete {{ opaque_id }}
|
26
|
+
</pre>
|
27
|
+
<p>We'll continue to send daily emails when this happens, so please do fix this up when you get a chance.</p>
|
28
|
+
<p>Please file an issue at {{ oss_repo }} or email <a href="mailto:{{ support_email }}">{{ support_email }}</a> if you need any help.</p>
|
29
|
+
|
30
|
+
{% partial 'signoff' %}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
{% expose subject %}WebhookDB: ICalendar Error{% endexpose %}
|
1
|
+
{% expose subject %}WebhookDB: ICalendar Error{% endexpose %}
|
2
2
|
|
3
3
|
{% partial 'greeting' %}
|
4
4
|
|
@@ -6,8 +6,9 @@
|
|
6
6
|
WebhookDB encountered an error syncing an ICalendar feed. We have the following information about the failure:
|
7
7
|
</p>
|
8
8
|
<ul>
|
9
|
+
<li>Organization: {{ org_name }} (key: <code>{{ org_key }}</code>)</li>
|
9
10
|
<li>Service Integration Name: {{ service_name }}, ID: <code>{{ opaque_id }}</code></li>
|
10
|
-
<li>Calendar ID (you
|
11
|
+
<li>Calendar ID (you sent this when adding the calendar): <code>{{ external_calendar_id }}</code></li>
|
11
12
|
<li>Request: <code>{{ request_method }} {{ request_url }}</code></li>
|
12
13
|
<li>Response Status: <code>{{ response_status }}</code></li>
|
13
14
|
<li>Body: <code>{{ response_body }}</code></li>
|
@@ -22,7 +23,12 @@
|
|
22
23
|
<p>
|
23
24
|
If this calendar is no longer shared, it should be removed.
|
24
25
|
Use a DELETE request, as per <a href="{{ docs_url }}/guides/icalendar/#delete">{{ docs_url }}/guides/icalendar/#delete</a>.
|
26
|
+
For example, here is the cURL to run to delete this from the shell:
|
25
27
|
</p>
|
28
|
+
<pre>
|
29
|
+
$ export WHDB_WEBHOOK_SECRET=`webhookdb integration info --org={{ org_key }} --field=webhook_secret {{ opaque_id }}`
|
30
|
+
$ curl -X POST -d '{"type":"DELETE","external_id":"{{ external_calendar_id }}"}' -H "Content-Type: application/json" -H "Whdb-Webhook-Secret: ${WHDB_WEBHOOK_SECRET}" "{{ webhook_endpoint }}"
|
31
|
+
</pre>
|
26
32
|
<p>We'll continue to send daily emails when this happens, so please do fix this up when you get a chance.</p>
|
27
33
|
<p>Please file an issue at {{ oss_repo }} if you need any help.</p>
|
28
34
|
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
Sequel.migration do
|
4
4
|
change do
|
5
|
+
# rubocop:disable Sequel/IrreversibleMigration
|
5
6
|
alter_table(:sync_targets) do
|
6
7
|
set_column_default :page_size, nil
|
7
8
|
end
|
8
9
|
alter_table(:organizations) do
|
9
10
|
set_column_default :minimum_sync_seconds, nil
|
10
11
|
end
|
12
|
+
# rubocop:enable Sequel/IrreversibleMigration
|
11
13
|
end
|
12
14
|
end
|
@@ -19,10 +19,12 @@ Sequel.migration do
|
|
19
19
|
:webhook_subscription_deliveries,
|
20
20
|
:webhook_subscriptions,
|
21
21
|
]
|
22
|
+
# rubocop:disable Sequel/IrreversibleMigration
|
22
23
|
searchable.each do |tbl|
|
23
24
|
alter_table(tbl) do
|
24
25
|
add_column :text_search, :tsvector
|
25
26
|
end
|
26
27
|
end
|
28
|
+
# rubocop:enable Sequel/IrreversibleMigration
|
27
29
|
end
|
28
30
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
change do
|
5
|
+
create_table(:system_log_events) do
|
6
|
+
primary_key :id, type: :bigserial
|
7
|
+
timestamptz :at, null: false, default: Sequel.function(:now), index: true
|
8
|
+
text :title, null: false, default: ""
|
9
|
+
text :body, null: false, default: ""
|
10
|
+
text :link, null: false, default: ""
|
11
|
+
foreign_key :actor_id, :customers
|
12
|
+
column :text_search, :tsvector
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
change do
|
5
|
+
alter_table(:service_integrations) do
|
6
|
+
add_index :depends_on_id
|
7
|
+
add_index :service_name
|
8
|
+
end
|
9
|
+
|
10
|
+
alter_table(:message_deliveries) do
|
11
|
+
add_index :soft_deleted_at
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
change do
|
5
|
+
create_table(:organization_error_handlers) do
|
6
|
+
primary_key :id
|
7
|
+
timestamptz :created_at, null: false, default: Sequel.function(:now)
|
8
|
+
timestamptz :updated_at
|
9
|
+
|
10
|
+
text :opaque_id, null: false, unique: true
|
11
|
+
|
12
|
+
foreign_key :organization_id, :organizations, null: false
|
13
|
+
foreign_key :created_by_id, :customers, null: true, on_delete: :set_null
|
14
|
+
|
15
|
+
text :url, null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
no_transaction
|
5
|
+
up do
|
6
|
+
# These are optimized for Webhookdb::LoggedWebhook.trim. See code for details/coupling.
|
7
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_unowned_idx ON logged_webhooks(inserted_at) " \
|
8
|
+
"WHERE organization_id IS NULL"
|
9
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_success_idx ON logged_webhooks(inserted_at) " \
|
10
|
+
"WHERE organization_id IS NOT NULL AND response_status < 400 AND truncated_at IS NULL"
|
11
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_delete_success_idx ON logged_webhooks(inserted_at) " \
|
12
|
+
"WHERE organization_id IS NOT NULL AND response_status < 400 AND truncated_at IS NOT NULL"
|
13
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_failures_idx ON logged_webhooks(inserted_at) " \
|
14
|
+
"WHERE organization_id IS NOT NULL AND response_status >= 400 AND truncated_at IS NULL"
|
15
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_delete_failures_idx ON logged_webhooks(inserted_at) " \
|
16
|
+
"WHERE organization_id IS NOT NULL AND response_status >= 400 AND truncated_at IS NOT NULL"
|
17
|
+
end
|
18
|
+
down do
|
19
|
+
run "DROP INDEX logged_webhooks_trim_unowned_idx"
|
20
|
+
run "DROP INDEX logged_webhooks_trim_success_idx"
|
21
|
+
run "DROP INDEX logged_webhooks_delete_success_idx"
|
22
|
+
run "DROP INDEX logged_webhooks_trim_failures_idx"
|
23
|
+
run "DROP INDEX logged_webhooks_delete_failures_idx"
|
24
|
+
end
|
25
|
+
end
|
data/integration/async_spec.rb
CHANGED
data/lib/amigo/durable_job.rb
CHANGED
@@ -351,8 +351,8 @@ module Amigo::DurableJob
|
|
351
351
|
# Space-separate multiple env vars.
|
352
352
|
setting :server_env_vars, ["DATABASE_URL"], convert: ->(s) { s.split.map(&:strip) }
|
353
353
|
|
354
|
-
setting :schema_name, :public, convert:
|
355
|
-
setting :table_name, :durable_jobs, convert:
|
354
|
+
setting :schema_name, :public, convert: lambda(&:to_sym)
|
355
|
+
setting :table_name, :durable_jobs, convert: lambda(&:to_sym)
|
356
356
|
|
357
357
|
after_configured do
|
358
358
|
self.storage_database_urls = self.server_urls.dup
|
data/lib/webhookdb/admin.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Webhookdb::Admin
|
4
|
+
module Linked
|
5
|
+
protected def _admin_datatype = self.class.dataset.first_source_table
|
6
|
+
protected def _admin_id = self.pk
|
7
|
+
protected def _admin_display = "show"
|
8
|
+
def admin_link = "#{Webhookdb.admin_url}/admin#/#{_admin_datatype}/#{_admin_id}/#{_admin_display}"
|
9
|
+
end
|
4
10
|
end
|
@@ -29,6 +29,7 @@ class Webhookdb::AdminAPI::DataProvider < Webhookdb::AdminAPI::V1
|
|
29
29
|
service_integrations: [Webhookdb::ServiceIntegration, ServiceIntegration],
|
30
30
|
subscriptions: [Webhookdb::Subscription, Subscription],
|
31
31
|
sync_targets: [Webhookdb::SyncTarget, SyncTarget],
|
32
|
+
system_log_events: [Webhookdb::SystemLogEvent, SystemLogEvent],
|
32
33
|
webhook_subscriptions: [Webhookdb::WebhookSubscription, WebhookSubscription],
|
33
34
|
webhook_subscription_deliveries: [Webhookdb::WebhookSubscription::Delivery, WebhookSubscriptionDelivery],
|
34
35
|
}.freeze
|
@@ -163,6 +163,14 @@ module Webhookdb::AdminAPI::Entities
|
|
163
163
|
expose :page_size
|
164
164
|
end
|
165
165
|
|
166
|
+
class SystemLogEvent < Base
|
167
|
+
expose :at
|
168
|
+
expose :title
|
169
|
+
expose :body
|
170
|
+
expose :link
|
171
|
+
expose :actor_id
|
172
|
+
end
|
173
|
+
|
166
174
|
class WebhookSubscription < Base
|
167
175
|
expose :organization, with: Related
|
168
176
|
expose :service_integration, with: Related
|
@@ -18,7 +18,7 @@ require "webhookdb" unless defined?(Webhookdb)
|
|
18
18
|
# end
|
19
19
|
# return ag.finish
|
20
20
|
#
|
21
|
-
class Webhookdb::AggregateResult <
|
21
|
+
class Webhookdb::AggregateResult < Webhookdb::WebhookdbError
|
22
22
|
attr_reader :successes, :failures, :errors
|
23
23
|
|
24
24
|
def initialize(existing=nil)
|
@@ -136,10 +136,12 @@ module Webhookdb::API
|
|
136
136
|
expose :opaque_id
|
137
137
|
expose :service_integration, with: ServiceIntegrationEntity
|
138
138
|
expose :period_seconds
|
139
|
+
expose :page_size
|
139
140
|
expose :displaysafe_connection_url, as: :connection_url
|
140
141
|
expose :table
|
141
142
|
expose :schema
|
142
143
|
expose :last_synced_at
|
144
|
+
expose :latency, &self.delegate_to(:latency, :to_i)
|
143
145
|
expose :associated_type
|
144
146
|
expose :associated_id
|
145
147
|
expose :associated_object_display
|
@@ -155,8 +157,9 @@ module Webhookdb::API
|
|
155
157
|
[:associated_object_display, "Associated"],
|
156
158
|
[:schema_and_table_string, "Table"],
|
157
159
|
[:last_synced_at, "Last Synced"],
|
160
|
+
[:latency, "Latency"],
|
158
161
|
[:period_seconds, "Period"],
|
159
|
-
[:page_size, "Page"],
|
162
|
+
[:page_size, "Page Size"],
|
160
163
|
]
|
161
164
|
end
|
162
165
|
end
|
@@ -168,8 +171,9 @@ module Webhookdb::API
|
|
168
171
|
[:connection_url, "URL"],
|
169
172
|
[:associated_object_display, "Associated"],
|
170
173
|
[:last_synced_at, "Last Synced"],
|
174
|
+
[:latency, "Latency"],
|
171
175
|
[:period_seconds, "Period"],
|
172
|
-
[:page_size, "Page"],
|
176
|
+
[:page_size, "Page Size"],
|
173
177
|
]
|
174
178
|
end
|
175
179
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/api"
|
4
|
+
|
5
|
+
class Webhookdb::API::ErrorHandlers < Webhookdb::API::V1
|
6
|
+
include Webhookdb::Service::Types
|
7
|
+
|
8
|
+
CREATE_PROMPT = "What is the URL that WebhookDB should POST to when a replicator fails? " \
|
9
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more information:".freeze
|
10
|
+
|
11
|
+
resource :organizations do
|
12
|
+
route_param :org_identifier do
|
13
|
+
resource :error_handlers do
|
14
|
+
helpers do
|
15
|
+
def lookup!
|
16
|
+
org = lookup_org!
|
17
|
+
eh = org.error_handlers_dataset[opaque_id: params[:opaque_id].strip]
|
18
|
+
merror!(403, "There is no error handler with that id.") if eh.nil?
|
19
|
+
set_request_tags(error_handler_opaque_id: eh.opaque_id)
|
20
|
+
return eh
|
21
|
+
end
|
22
|
+
|
23
|
+
def guard_editable!(customer, org)
|
24
|
+
return if has_admin?(org, customer:)
|
25
|
+
permission_error!("You must be an org admin to modify error handlers.")
|
26
|
+
end
|
27
|
+
|
28
|
+
def url_valid?(u)
|
29
|
+
begin
|
30
|
+
uri = URI(u)
|
31
|
+
rescue URI::InvalidURIError
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
return true if uri.scheme == "sentry"
|
35
|
+
return false unless uri.scheme&.start_with?("http")
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Returns a list of all error handlers associated with the org."
|
41
|
+
get do
|
42
|
+
handlers = lookup_org!.error_handlers
|
43
|
+
message = ""
|
44
|
+
if handlers.empty?
|
45
|
+
message = "This organization doesn't have any error handlers yet.\n" \
|
46
|
+
"Use `webhookdb error-handler create` to set one up."
|
47
|
+
end
|
48
|
+
present_collection handlers, with: ErrorHandlerEntity, message:
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Creates a new error handler."
|
52
|
+
params do
|
53
|
+
optional :url, type: TrimmedString, prompt: CREATE_PROMPT
|
54
|
+
end
|
55
|
+
post :create do
|
56
|
+
cust = current_customer
|
57
|
+
org = lookup_org!
|
58
|
+
guard_editable!(cust, org)
|
59
|
+
unless url_valid?(params[:url])
|
60
|
+
msg = "URL is malformed. It should be a URL like https://foo.bar/path, http://foo.bar:123, etc. " \
|
61
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more info."
|
62
|
+
merror!(400, msg)
|
63
|
+
end
|
64
|
+
eh = Webhookdb::Organization::ErrorHandler.create(
|
65
|
+
organization: org,
|
66
|
+
url: params[:url],
|
67
|
+
created_by: cust,
|
68
|
+
)
|
69
|
+
message = "Whenever one of your replicators errors, WebhookDB will alert the given URL. " \
|
70
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more information."
|
71
|
+
status 200
|
72
|
+
present eh, with: ErrorHandlerEntity, message:
|
73
|
+
end
|
74
|
+
|
75
|
+
route_param :opaque_id, type: String do
|
76
|
+
get do
|
77
|
+
eh = lookup!
|
78
|
+
status 200
|
79
|
+
present eh, with: ErrorHandlerEntity
|
80
|
+
end
|
81
|
+
|
82
|
+
post :delete do
|
83
|
+
customer = current_customer
|
84
|
+
eh = lookup!
|
85
|
+
guard_editable!(customer, eh.organization)
|
86
|
+
eh.destroy
|
87
|
+
status 200
|
88
|
+
present eh, with: ErrorHandlerEntity,
|
89
|
+
message: "You have successfully deleted the error handler '#{eh.opaque_id}'."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ErrorHandlerEntity < Webhookdb::API::BaseEntity
|
97
|
+
expose :opaque_id, as: :id
|
98
|
+
expose :url
|
99
|
+
|
100
|
+
def self.display_headers
|
101
|
+
return [[:id, "ID"], [:url, "Url"]]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -122,7 +122,14 @@ module Webhookdb::API::Helpers
|
|
122
122
|
sints = org.service_integrations_dataset.
|
123
123
|
where(Sequel[service_name: identifier] | Sequel[table_name: identifier] | Sequel[opaque_id: identifier]).
|
124
124
|
limit(2).all
|
125
|
-
|
125
|
+
if sints.size == 1
|
126
|
+
sint = sints.first
|
127
|
+
set_request_tags(
|
128
|
+
service_integration_service_name: sint.service_name,
|
129
|
+
service_integration_table_name: sint.table_name,
|
130
|
+
)
|
131
|
+
return sint
|
132
|
+
end
|
126
133
|
merror!(403, "There is no service integration with that identifier.") if sints.empty?
|
127
134
|
dupe_attr = nil
|
128
135
|
alternative = nil
|
@@ -171,6 +178,23 @@ module Webhookdb::API::Helpers
|
|
171
178
|
sint = yield
|
172
179
|
return if sint == :pass
|
173
180
|
raise "error instead of return nil if there is no service integration" if sint.nil?
|
181
|
+
# If this is a valid GET request (ie, the replicator doesn't do useful auth),
|
182
|
+
# and it's from a bot, we want to block it, otherwise we'd process invalid webhooks.
|
183
|
+
#
|
184
|
+
# This is almost never a problem, because almost all services perform some validation,
|
185
|
+
# and the auth info isn't in a GET URL; but in some cases, that won't be the case,
|
186
|
+
# and we protect against it here.
|
187
|
+
#
|
188
|
+
# An example would be a bot getting a link preview.
|
189
|
+
if request.get? && !request.GET["skipbotcheck"] && Browser.new(request.user_agent).bot?
|
190
|
+
# IMPORTANT: Run the Browser code last since it has to run a bunch of regexes to detect a bot.
|
191
|
+
env["api.format"] = :binary
|
192
|
+
header "Content-Type", "text/plain"
|
193
|
+
body("This route is for receiving webhooks and HTTP calls, not for bots. " \
|
194
|
+
"Call this endpoint with the query param 'skipbotcheck=true' to bypass this check.")
|
195
|
+
status 403
|
196
|
+
return
|
197
|
+
end
|
174
198
|
opaque_id = sint.opaque_id
|
175
199
|
organization_id = sint.organization_id
|
176
200
|
svc = Webhookdb::Replicator.create(sint).dispatch_request_to(request)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/api"
|
4
|
+
require "webhookdb/jobs/icalendar_enqueue_syncs_for_urls"
|
5
|
+
|
6
|
+
class Webhookdb::API::Icalproxy < Webhookdb::API::V1
|
7
|
+
resource :icalproxy do
|
8
|
+
resource :webhook do
|
9
|
+
post do
|
10
|
+
merror!(402, "Api key not configured") unless Webhookdb::Icalendar.proxy_api_key.present?
|
11
|
+
request_key = request.env["HTTP_AUTHORIZATION"] || ""
|
12
|
+
configured_key = "Apikey #{Webhookdb::Icalendar.proxy_api_key}"
|
13
|
+
verified = request_key && ActiveSupport::SecurityUtils.secure_compare(request_key, configured_key)
|
14
|
+
merror!(401, "Invalid api key") unless verified
|
15
|
+
urls = params.fetch(:urls)
|
16
|
+
Webhookdb::Jobs::IcalendarEnqueueSyncsForUrls.perform_async(urls)
|
17
|
+
status 202
|
18
|
+
present({o: "k"})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -13,6 +13,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
13
13
|
def lookup_session!
|
14
14
|
session = Webhookdb::Oauth::Session.usable.where(oauth_state: params[:state]).first
|
15
15
|
error!("Forbidden", 302, {"Location" => "/v1/install/#{oauth_provider.key}/forbidden"}) if session.nil?
|
16
|
+
set_request_tags(session_id: session.id, session_state: session.oauth_state)
|
16
17
|
return session
|
17
18
|
end
|
18
19
|
|
@@ -73,7 +74,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
73
74
|
def exchange_authorization_code(code)
|
74
75
|
return oauth_provider.exchange_authorization_code(code:)
|
75
76
|
rescue Webhookdb::Http::Error => e
|
76
|
-
logger.warn "oauth_exchange_error",
|
77
|
+
logger.warn "oauth_exchange_error", e
|
77
78
|
url = "#{Webhookdb.api_url}/v1/install/#{oauth_provider.key}"
|
78
79
|
raise FormError.new(
|
79
80
|
"Something went wrong getting your access token from #{oauth_provider.app_name}. " \
|
@@ -206,6 +206,12 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
206
206
|
{title: "Customer", value: "(#{c.id}) #{c.email}", short: false},
|
207
207
|
],
|
208
208
|
).emit
|
209
|
+
Webhookdb::SystemLogEvent.create(
|
210
|
+
title: "Organization Closure Requested",
|
211
|
+
body: "#{org.name} (#{org.key})",
|
212
|
+
link: org.admin_link,
|
213
|
+
actor: c,
|
214
|
+
)
|
209
215
|
step = Webhookdb::Replicator::StateMachineStep.new.completed
|
210
216
|
step.output = "Thanks! We've received the request to close your #{org.name} organization. " \
|
211
217
|
"We'll be in touch within 2 business days confirming removal."
|
@@ -15,6 +15,7 @@ class Webhookdb::API::SavedQueries < Webhookdb::API::V1
|
|
15
15
|
# We can add other identifiers in the future
|
16
16
|
cq = org.saved_queries_dataset[opaque_id: params[:query_identifier]]
|
17
17
|
merror!(403, "There is no saved query with that identifier.") if cq.nil?
|
18
|
+
set_request_tags(saved_query_opaque_id: cq.opaque_id)
|
18
19
|
return cq
|
19
20
|
end
|
20
21
|
|
@@ -13,6 +13,7 @@ class Webhookdb::API::SavedViews < Webhookdb::API::V1
|
|
13
13
|
org = lookup_org!
|
14
14
|
cq = org.saved_views_dataset[name: params[:name].strip]
|
15
15
|
merror!(403, "There is no view with that name.") if cq.nil?
|
16
|
+
set_request_tags(saved_view_name: cq.name)
|
16
17
|
return cq
|
17
18
|
end
|
18
19
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "browser"
|
3
4
|
require "grape"
|
4
5
|
require "oj"
|
5
6
|
|
@@ -178,7 +179,7 @@ If the list does not look correct, you can contact support at #{Webhookdb.suppor
|
|
178
179
|
begin
|
179
180
|
svc.upsert_webhook_body(body)
|
180
181
|
rescue KeyError, TypeError => e
|
181
|
-
self.logger.error "immediate_upsert",
|
182
|
+
self.logger.error "immediate_upsert", e
|
182
183
|
err_msg = "Sorry! Looks like something has gone wrong. " \
|
183
184
|
"Check your schema or contact support at #{Webhookdb.support_email}."
|
184
185
|
merror!(400, err_msg)
|
@@ -151,6 +151,7 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
|
|
151
151
|
(stgt = org.all_sync_targets_dataset[Sequel[:sync_targets][:opaque_id] => params[:opaque_id]])
|
152
152
|
merror!(403, "There is no #{fullname} sync target with that id.") if
|
153
153
|
stgt.nil? || !stgt.send(predicate)
|
154
|
+
set_request_tags(stgt.log_tags)
|
154
155
|
return stgt
|
155
156
|
end
|
156
157
|
end
|
@@ -204,7 +205,6 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
|
|
204
205
|
)
|
205
206
|
end
|
206
207
|
|
207
|
-
stgt.logger.warn("destroying_sync_target", sync_target_id: stgt.id, customer_id: current_customer.id)
|
208
208
|
stgt.destroy
|
209
209
|
status 200
|
210
210
|
message = "#{fullname.capitalize} sync target has been removed and will no longer sync."
|
data/lib/webhookdb/api/system.rb
CHANGED
@@ -52,6 +52,7 @@ class Webhookdb::API::WebhookSubscriptions < Webhookdb::API::V1
|
|
52
52
|
org = lookup_org!
|
53
53
|
whsub = org.all_webhook_subscriptions_dataset[opaque_id: params[:opaque_id]]
|
54
54
|
merror!(403, "No webhook subscription with that ID exists in that organization.") if whsub.nil?
|
55
|
+
set_request_tags(webhook_subscription_id: whsub.id)
|
55
56
|
return whsub
|
56
57
|
end
|
57
58
|
end
|
data/lib/webhookdb/api.rb
CHANGED
@@ -52,11 +52,14 @@ module Webhookdb::API
|
|
52
52
|
memberships = customer.verified_memberships_dataset.where(organization: orgs).limit(2).all
|
53
53
|
permission_error!("You don't have permissions with that organization.") if memberships.empty?
|
54
54
|
merror!(500, "ambiguous", alert: true) if memberships.size > 1 # TODO: better message, tests
|
55
|
-
|
55
|
+
org = memberships.first.organization
|
56
|
+
set_request_tags(organization: org.key)
|
57
|
+
return org
|
56
58
|
end
|
57
59
|
raise "something went wrong" unless allow_connstr_auth
|
58
60
|
org = Webhookdb::API::ConnstrAuth.find_authed(orgs, request)
|
59
61
|
unauthenticated! if org.nil?
|
62
|
+
set_request_tags(organization: org.key)
|
60
63
|
return org
|
61
64
|
end
|
62
65
|
|