webhookdb 0.1.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 +7 -0
- data/data/messages/layouts/blank.email.liquid +10 -0
- data/data/messages/layouts/minimal.email.liquid +28 -0
- data/data/messages/layouts/standard.email.liquid +28 -0
- data/data/messages/partials/button.liquid +15 -0
- data/data/messages/partials/environment_banner.liquid +9 -0
- data/data/messages/partials/footer.liquid +22 -0
- data/data/messages/partials/greeting.liquid +3 -0
- data/data/messages/partials/logo_header.liquid +18 -0
- data/data/messages/partials/signoff.liquid +1 -0
- data/data/messages/styles/v1.liquid +346 -0
- data/data/messages/templates/errors/icalendar_fetch.email.liquid +29 -0
- data/data/messages/templates/invite.email.liquid +15 -0
- data/data/messages/templates/new_customer.email.liquid +24 -0
- data/data/messages/templates/org_database_migration_finished.email.liquid +7 -0
- data/data/messages/templates/org_database_migration_started.email.liquid +9 -0
- data/data/messages/templates/specs/_field_partial.liquid +1 -0
- data/data/messages/templates/specs/basic.email.liquid +2 -0
- data/data/messages/templates/specs/basic.fake.liquid +1 -0
- data/data/messages/templates/specs/with_field.email.liquid +2 -0
- data/data/messages/templates/specs/with_field.fake.liquid +1 -0
- data/data/messages/templates/specs/with_include.email.liquid +2 -0
- data/data/messages/templates/specs/with_partial.email.liquid +1 -0
- data/data/messages/templates/verification.email.liquid +14 -0
- data/data/messages/templates/verification.sms.liquid +1 -0
- data/data/messages/web/install-customer-login.liquid +48 -0
- data/data/messages/web/install-error.liquid +17 -0
- data/data/messages/web/install-success.liquid +35 -0
- data/data/messages/web/install.liquid +20 -0
- data/data/messages/web/partials/footer.liquid +4 -0
- data/data/messages/web/partials/form_error.liquid +1 -0
- data/data/messages/web/partials/header.liquid +3 -0
- data/data/messages/web/styles.liquid +134 -0
- data/data/windows_tz.txt +461 -0
- data/db/migrations/001_testing_pixies.rb +13 -0
- data/db/migrations/002_initial.rb +132 -0
- data/db/migrations/003_ux_overhaul.rb +20 -0
- data/db/migrations/004_incremental_backfill.rb +9 -0
- data/db/migrations/005_log_webhooks.rb +24 -0
- data/db/migrations/006_generalize_roles.rb +29 -0
- data/db/migrations/007_org_dns.rb +12 -0
- data/db/migrations/008_webhook_subscriptions.rb +19 -0
- data/db/migrations/009_nonunique_stripe_subscription_customer.rb +16 -0
- data/db/migrations/010_drop_integration_soft_delete.rb +14 -0
- data/db/migrations/011_webhook_subscriptions_created_at.rb +10 -0
- data/db/migrations/012_webhook_subscriptions_created_by.rb +9 -0
- data/db/migrations/013_default_org_membership.rb +30 -0
- data/db/migrations/014_webhook_subscription_deliveries.rb +26 -0
- data/db/migrations/015_dependent_integrations.rb +9 -0
- data/db/migrations/016_encrypted_columns.rb +9 -0
- data/db/migrations/017_skip_verification.rb +9 -0
- data/db/migrations/018_sync_targets.rb +25 -0
- data/db/migrations/019_org_schema.rb +9 -0
- data/db/migrations/020_org_database_migrations.rb +25 -0
- data/db/migrations/021_no_default_org_schema.rb +14 -0
- data/db/migrations/022_database_document.rb +15 -0
- data/db/migrations/023_sync_target_schema.rb +9 -0
- data/db/migrations/024_org_semaphore_jobs.rb +9 -0
- data/db/migrations/025_integration_backfill_cursor.rb +9 -0
- data/db/migrations/026_undo_integration_backfill_cursor.rb +9 -0
- data/db/migrations/027_sync_target_http_sync.rb +12 -0
- data/db/migrations/028_logged_webhook_path.rb +24 -0
- data/db/migrations/029_encrypt_columns.rb +97 -0
- data/db/migrations/030_org_sync_target_timeout.rb +9 -0
- data/db/migrations/031_org_max_query_rows.rb +9 -0
- data/db/migrations/032_remove_db_defaults.rb +12 -0
- data/db/migrations/033_backfill_jobs.rb +26 -0
- data/db/migrations/034_backfill_job_criteria.rb +9 -0
- data/db/migrations/035_synchronous_backfill.rb +9 -0
- data/db/migrations/036_oauth.rb +26 -0
- data/db/migrations/037_oauth_used.rb +9 -0
- data/lib/amigo/durable_job.rb +416 -0
- data/lib/pry/clipboard.rb +111 -0
- data/lib/sequel/advisory_lock.rb +65 -0
- data/lib/webhookdb/admin.rb +4 -0
- data/lib/webhookdb/admin_api/auth.rb +36 -0
- data/lib/webhookdb/admin_api/customers.rb +63 -0
- data/lib/webhookdb/admin_api/database_documents.rb +20 -0
- data/lib/webhookdb/admin_api/entities.rb +66 -0
- data/lib/webhookdb/admin_api/message_deliveries.rb +61 -0
- data/lib/webhookdb/admin_api/roles.rb +15 -0
- data/lib/webhookdb/admin_api.rb +34 -0
- data/lib/webhookdb/aggregate_result.rb +63 -0
- data/lib/webhookdb/api/auth.rb +122 -0
- data/lib/webhookdb/api/connstr_auth.rb +36 -0
- data/lib/webhookdb/api/db.rb +188 -0
- data/lib/webhookdb/api/demo.rb +14 -0
- data/lib/webhookdb/api/entities.rb +198 -0
- data/lib/webhookdb/api/helpers.rb +253 -0
- data/lib/webhookdb/api/install.rb +296 -0
- data/lib/webhookdb/api/me.rb +53 -0
- data/lib/webhookdb/api/organizations.rb +254 -0
- data/lib/webhookdb/api/replay.rb +64 -0
- data/lib/webhookdb/api/service_integrations.rb +402 -0
- data/lib/webhookdb/api/services.rb +27 -0
- data/lib/webhookdb/api/stripe.rb +22 -0
- data/lib/webhookdb/api/subscriptions.rb +67 -0
- data/lib/webhookdb/api/sync_targets.rb +232 -0
- data/lib/webhookdb/api/system.rb +37 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +96 -0
- data/lib/webhookdb/api.rb +92 -0
- data/lib/webhookdb/apps.rb +93 -0
- data/lib/webhookdb/async/audit_logger.rb +38 -0
- data/lib/webhookdb/async/autoscaler.rb +84 -0
- data/lib/webhookdb/async/job.rb +18 -0
- data/lib/webhookdb/async/job_logger.rb +45 -0
- data/lib/webhookdb/async/scheduled_job.rb +18 -0
- data/lib/webhookdb/async.rb +142 -0
- data/lib/webhookdb/aws.rb +98 -0
- data/lib/webhookdb/backfill_job.rb +107 -0
- data/lib/webhookdb/backfiller.rb +107 -0
- data/lib/webhookdb/cloudflare.rb +39 -0
- data/lib/webhookdb/connection_cache.rb +177 -0
- data/lib/webhookdb/console.rb +71 -0
- data/lib/webhookdb/convertkit.rb +14 -0
- data/lib/webhookdb/crypto.rb +66 -0
- data/lib/webhookdb/customer/reset_code.rb +94 -0
- data/lib/webhookdb/customer.rb +347 -0
- data/lib/webhookdb/database_document.rb +72 -0
- data/lib/webhookdb/db_adapter/column_types.rb +37 -0
- data/lib/webhookdb/db_adapter/default_sql.rb +187 -0
- data/lib/webhookdb/db_adapter/pg.rb +96 -0
- data/lib/webhookdb/db_adapter/snowflake.rb +137 -0
- data/lib/webhookdb/db_adapter.rb +208 -0
- data/lib/webhookdb/dbutil.rb +92 -0
- data/lib/webhookdb/demo_mode.rb +100 -0
- data/lib/webhookdb/developer_alert.rb +51 -0
- data/lib/webhookdb/email_octopus.rb +21 -0
- data/lib/webhookdb/enumerable.rb +18 -0
- data/lib/webhookdb/fixtures/backfill_jobs.rb +72 -0
- data/lib/webhookdb/fixtures/customers.rb +65 -0
- data/lib/webhookdb/fixtures/database_documents.rb +27 -0
- data/lib/webhookdb/fixtures/faker.rb +41 -0
- data/lib/webhookdb/fixtures/logged_webhooks.rb +56 -0
- data/lib/webhookdb/fixtures/message_deliveries.rb +59 -0
- data/lib/webhookdb/fixtures/oauth_sessions.rb +24 -0
- data/lib/webhookdb/fixtures/organization_database_migrations.rb +37 -0
- data/lib/webhookdb/fixtures/organization_memberships.rb +54 -0
- data/lib/webhookdb/fixtures/organizations.rb +32 -0
- data/lib/webhookdb/fixtures/reset_codes.rb +23 -0
- data/lib/webhookdb/fixtures/service_integrations.rb +42 -0
- data/lib/webhookdb/fixtures/subscriptions.rb +33 -0
- data/lib/webhookdb/fixtures/sync_targets.rb +32 -0
- data/lib/webhookdb/fixtures/webhook_subscriptions.rb +35 -0
- data/lib/webhookdb/fixtures.rb +15 -0
- data/lib/webhookdb/formatting.rb +56 -0
- data/lib/webhookdb/front.rb +49 -0
- data/lib/webhookdb/github.rb +22 -0
- data/lib/webhookdb/google_calendar.rb +29 -0
- data/lib/webhookdb/heroku.rb +21 -0
- data/lib/webhookdb/http.rb +114 -0
- data/lib/webhookdb/icalendar.rb +17 -0
- data/lib/webhookdb/id.rb +17 -0
- data/lib/webhookdb/idempotency.rb +90 -0
- data/lib/webhookdb/increase.rb +42 -0
- data/lib/webhookdb/intercom.rb +23 -0
- data/lib/webhookdb/jobs/amigo_test_jobs.rb +118 -0
- data/lib/webhookdb/jobs/backfill.rb +32 -0
- data/lib/webhookdb/jobs/create_mirror_table.rb +18 -0
- data/lib/webhookdb/jobs/create_stripe_customer.rb +17 -0
- data/lib/webhookdb/jobs/customer_created_notify_internal.rb +22 -0
- data/lib/webhookdb/jobs/demo_mode_sync_data.rb +19 -0
- data/lib/webhookdb/jobs/deprecated_jobs.rb +19 -0
- data/lib/webhookdb/jobs/developer_alert_handle.rb +14 -0
- data/lib/webhookdb/jobs/durable_job_recheck_poller.rb +17 -0
- data/lib/webhookdb/jobs/emailer.rb +15 -0
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +25 -0
- data/lib/webhookdb/jobs/icalendar_sync.rb +23 -0
- data/lib/webhookdb/jobs/logged_webhook_replay.rb +17 -0
- data/lib/webhookdb/jobs/logged_webhook_resilient_replay.rb +15 -0
- data/lib/webhookdb/jobs/message_dispatched.rb +16 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_run.rb +24 -0
- data/lib/webhookdb/jobs/prepare_database_connections.rb +22 -0
- data/lib/webhookdb/jobs/process_webhook.rb +47 -0
- data/lib/webhookdb/jobs/renew_watch_channel.rb +24 -0
- data/lib/webhookdb/jobs/replication_migration.rb +24 -0
- data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +23 -0
- data/lib/webhookdb/jobs/scheduled_backfills.rb +77 -0
- data/lib/webhookdb/jobs/send_invite.rb +15 -0
- data/lib/webhookdb/jobs/send_test_webhook.rb +25 -0
- data/lib/webhookdb/jobs/send_webhook.rb +20 -0
- data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +16 -0
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +38 -0
- data/lib/webhookdb/jobs/trim_logged_webhooks.rb +15 -0
- data/lib/webhookdb/jobs/webhook_resource_notify_integrations.rb +30 -0
- data/lib/webhookdb/jobs/webhook_subscription_delivery_attempt.rb +29 -0
- data/lib/webhookdb/jobs.rb +4 -0
- data/lib/webhookdb/json.rb +113 -0
- data/lib/webhookdb/liquid/expose.rb +27 -0
- data/lib/webhookdb/liquid/filters.rb +16 -0
- data/lib/webhookdb/liquid/liquification.rb +26 -0
- data/lib/webhookdb/liquid/partial.rb +12 -0
- data/lib/webhookdb/logged_webhook/resilient.rb +95 -0
- data/lib/webhookdb/logged_webhook.rb +194 -0
- data/lib/webhookdb/message/body.rb +25 -0
- data/lib/webhookdb/message/delivery.rb +127 -0
- data/lib/webhookdb/message/email_transport.rb +133 -0
- data/lib/webhookdb/message/fake_transport.rb +54 -0
- data/lib/webhookdb/message/liquid_drops.rb +29 -0
- data/lib/webhookdb/message/template.rb +89 -0
- data/lib/webhookdb/message/transport.rb +43 -0
- data/lib/webhookdb/message.rb +150 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +42 -0
- data/lib/webhookdb/messages/invite.rb +23 -0
- data/lib/webhookdb/messages/new_customer.rb +14 -0
- data/lib/webhookdb/messages/org_database_migration_finished.rb +23 -0
- data/lib/webhookdb/messages/org_database_migration_started.rb +24 -0
- data/lib/webhookdb/messages/specs.rb +57 -0
- data/lib/webhookdb/messages/verification.rb +23 -0
- data/lib/webhookdb/method_utilities.rb +82 -0
- data/lib/webhookdb/microsoft_calendar.rb +36 -0
- data/lib/webhookdb/nextpax.rb +14 -0
- data/lib/webhookdb/oauth/front.rb +58 -0
- data/lib/webhookdb/oauth/intercom.rb +58 -0
- data/lib/webhookdb/oauth/session.rb +24 -0
- data/lib/webhookdb/oauth.rb +80 -0
- data/lib/webhookdb/organization/alerting.rb +35 -0
- data/lib/webhookdb/organization/database_migration.rb +151 -0
- data/lib/webhookdb/organization/db_builder.rb +429 -0
- data/lib/webhookdb/organization.rb +506 -0
- data/lib/webhookdb/organization_membership.rb +58 -0
- data/lib/webhookdb/phone_number.rb +38 -0
- data/lib/webhookdb/plaid.rb +23 -0
- data/lib/webhookdb/platform.rb +27 -0
- data/lib/webhookdb/plivo.rb +52 -0
- data/lib/webhookdb/postgres/maintenance.rb +166 -0
- data/lib/webhookdb/postgres/model.rb +82 -0
- data/lib/webhookdb/postgres/model_utilities.rb +382 -0
- data/lib/webhookdb/postgres/testing_pixie.rb +16 -0
- data/lib/webhookdb/postgres/validations.rb +46 -0
- data/lib/webhookdb/postgres.rb +176 -0
- data/lib/webhookdb/postmark.rb +20 -0
- data/lib/webhookdb/redis.rb +35 -0
- data/lib/webhookdb/replicator/atom_single_feed_v1.rb +116 -0
- data/lib/webhookdb/replicator/aws_pricing_v1.rb +488 -0
- data/lib/webhookdb/replicator/base.rb +1185 -0
- data/lib/webhookdb/replicator/column.rb +482 -0
- data/lib/webhookdb/replicator/convertkit_broadcast_v1.rb +69 -0
- data/lib/webhookdb/replicator/convertkit_subscriber_v1.rb +200 -0
- data/lib/webhookdb/replicator/convertkit_tag_v1.rb +66 -0
- data/lib/webhookdb/replicator/convertkit_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/docgen.rb +167 -0
- data/lib/webhookdb/replicator/email_octopus_campaign_v1.rb +84 -0
- data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +159 -0
- data/lib/webhookdb/replicator/email_octopus_event_v1.rb +244 -0
- data/lib/webhookdb/replicator/email_octopus_list_v1.rb +101 -0
- data/lib/webhookdb/replicator/fake.rb +453 -0
- data/lib/webhookdb/replicator/front_conversation_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_marketplace_root_v1.rb +55 -0
- data/lib/webhookdb/replicator/front_message_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_v1_mixin.rb +22 -0
- data/lib/webhookdb/replicator/github_issue_comment_v1.rb +58 -0
- data/lib/webhookdb/replicator/github_issue_v1.rb +83 -0
- data/lib/webhookdb/replicator/github_pull_v1.rb +84 -0
- data/lib/webhookdb/replicator/github_release_v1.rb +47 -0
- data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +250 -0
- data/lib/webhookdb/replicator/github_repository_event_v1.rb +45 -0
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +465 -0
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +334 -0
- data/lib/webhookdb/replicator/increase_account_number_v1.rb +77 -0
- data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/increase_account_v1.rb +63 -0
- data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +64 -0
- data/lib/webhookdb/replicator/increase_limit_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_transaction_v1.rb +74 -0
- data/lib/webhookdb/replicator/increase_v1_mixin.rb +121 -0
- data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +36 -0
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +38 -0
- data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +69 -0
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +105 -0
- data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +65 -0
- data/lib/webhookdb/replicator/plivo_sms_inbound_v1.rb +102 -0
- data/lib/webhookdb/replicator/postmark_inbound_message_v1.rb +94 -0
- data/lib/webhookdb/replicator/postmark_outbound_message_event_v1.rb +107 -0
- data/lib/webhookdb/replicator/schema_modification.rb +42 -0
- data/lib/webhookdb/replicator/shopify_customer_v1.rb +58 -0
- data/lib/webhookdb/replicator/shopify_order_v1.rb +64 -0
- data/lib/webhookdb/replicator/shopify_v1_mixin.rb +161 -0
- data/lib/webhookdb/replicator/signalwire_message_v1.rb +169 -0
- data/lib/webhookdb/replicator/sponsy_customer_v1.rb +54 -0
- data/lib/webhookdb/replicator/sponsy_placement_v1.rb +34 -0
- data/lib/webhookdb/replicator/sponsy_publication_v1.rb +125 -0
- data/lib/webhookdb/replicator/sponsy_slot_v1.rb +41 -0
- data/lib/webhookdb/replicator/sponsy_status_v1.rb +35 -0
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +165 -0
- data/lib/webhookdb/replicator/state_machine_step.rb +69 -0
- data/lib/webhookdb/replicator/stripe_charge_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_coupon_v1.rb +62 -0
- data/lib/webhookdb/replicator/stripe_customer_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_dispute_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_invoice_item_v1.rb +82 -0
- data/lib/webhookdb/replicator/stripe_invoice_v1.rb +116 -0
- data/lib/webhookdb/replicator/stripe_payout_v1.rb +67 -0
- data/lib/webhookdb/replicator/stripe_price_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_product_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_refund_v1.rb +101 -0
- data/lib/webhookdb/replicator/stripe_subscription_item_v1.rb +56 -0
- data/lib/webhookdb/replicator/stripe_subscription_v1.rb +75 -0
- data/lib/webhookdb/replicator/stripe_v1_mixin.rb +116 -0
- data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +141 -0
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +169 -0
- data/lib/webhookdb/replicator/transistor_show_v1.rb +68 -0
- data/lib/webhookdb/replicator/transistor_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/twilio_sms_v1.rb +156 -0
- data/lib/webhookdb/replicator/webhook_request.rb +5 -0
- data/lib/webhookdb/replicator/webhookdb_customer_v1.rb +74 -0
- data/lib/webhookdb/replicator.rb +224 -0
- data/lib/webhookdb/role.rb +42 -0
- data/lib/webhookdb/sentry.rb +35 -0
- data/lib/webhookdb/service/auth.rb +138 -0
- data/lib/webhookdb/service/collection.rb +91 -0
- data/lib/webhookdb/service/entities.rb +97 -0
- data/lib/webhookdb/service/helpers.rb +270 -0
- data/lib/webhookdb/service/middleware.rb +124 -0
- data/lib/webhookdb/service/types.rb +30 -0
- data/lib/webhookdb/service/validators.rb +32 -0
- data/lib/webhookdb/service/view_api.rb +63 -0
- data/lib/webhookdb/service.rb +219 -0
- data/lib/webhookdb/service_integration.rb +332 -0
- data/lib/webhookdb/shopify.rb +35 -0
- data/lib/webhookdb/signalwire.rb +13 -0
- data/lib/webhookdb/slack.rb +68 -0
- data/lib/webhookdb/snowflake.rb +90 -0
- data/lib/webhookdb/spec_helpers/async.rb +122 -0
- data/lib/webhookdb/spec_helpers/citest.rb +88 -0
- data/lib/webhookdb/spec_helpers/integration.rb +121 -0
- data/lib/webhookdb/spec_helpers/message.rb +41 -0
- data/lib/webhookdb/spec_helpers/postgres.rb +220 -0
- data/lib/webhookdb/spec_helpers/service.rb +432 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_columns.rb +56 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +915 -0
- data/lib/webhookdb/spec_helpers/whdb.rb +139 -0
- data/lib/webhookdb/spec_helpers.rb +63 -0
- data/lib/webhookdb/sponsy.rb +14 -0
- data/lib/webhookdb/stripe.rb +37 -0
- data/lib/webhookdb/subscription.rb +203 -0
- data/lib/webhookdb/sync_target.rb +491 -0
- data/lib/webhookdb/tasks/admin.rb +49 -0
- data/lib/webhookdb/tasks/annotate.rb +36 -0
- data/lib/webhookdb/tasks/db.rb +82 -0
- data/lib/webhookdb/tasks/docs.rb +42 -0
- data/lib/webhookdb/tasks/fixture.rb +35 -0
- data/lib/webhookdb/tasks/message.rb +50 -0
- data/lib/webhookdb/tasks/regress.rb +87 -0
- data/lib/webhookdb/tasks/release.rb +27 -0
- data/lib/webhookdb/tasks/sidekiq.rb +23 -0
- data/lib/webhookdb/tasks/specs.rb +64 -0
- data/lib/webhookdb/theranest.rb +15 -0
- data/lib/webhookdb/transistor.rb +13 -0
- data/lib/webhookdb/twilio.rb +13 -0
- data/lib/webhookdb/typed_struct.rb +44 -0
- data/lib/webhookdb/version.rb +5 -0
- data/lib/webhookdb/webhook_response.rb +50 -0
- data/lib/webhookdb/webhook_subscription/delivery.rb +82 -0
- data/lib/webhookdb/webhook_subscription.rb +226 -0
- data/lib/webhookdb/windows_tz.rb +32 -0
- data/lib/webhookdb/xml.rb +92 -0
- data/lib/webhookdb.rb +224 -0
- data/lib/webterm/apps.rb +45 -0
- metadata +1129 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faker"
|
|
4
|
+
require "fluent_fixtures"
|
|
5
|
+
|
|
6
|
+
require "webhookdb"
|
|
7
|
+
|
|
8
|
+
module Webhookdb::Fixtures
|
|
9
|
+
extend FluentFixtures::Collection
|
|
10
|
+
|
|
11
|
+
# Set the path to use when finding fixtures for this collection
|
|
12
|
+
fixture_path_prefix "webhookdb/fixtures"
|
|
13
|
+
|
|
14
|
+
::Faker::Config.locale = :en
|
|
15
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::Formatting
|
|
4
|
+
def self.blocks
|
|
5
|
+
return Blocks.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Blocks
|
|
9
|
+
def initialize
|
|
10
|
+
@arr = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def blank
|
|
14
|
+
return self.line("")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def line(value)
|
|
18
|
+
@arr << Line.new(value)
|
|
19
|
+
return self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def table(headers, rows)
|
|
23
|
+
@arr << Table.new(headers, rows)
|
|
24
|
+
return self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def as_json(*a)
|
|
28
|
+
return @arr.as_json(*a)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Line
|
|
33
|
+
attr_accessor :value
|
|
34
|
+
|
|
35
|
+
def initialize(value)
|
|
36
|
+
@value = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def as_json(*)
|
|
40
|
+
return {type: "line", value: self.value}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Table
|
|
45
|
+
attr_accessor :headers, :rows
|
|
46
|
+
|
|
47
|
+
def initialize(headers, rows)
|
|
48
|
+
@headers = headers
|
|
49
|
+
@rows = rows
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def as_json(*o)
|
|
53
|
+
return {type: "table", value: {headers: self.headers, rows: self.rows.as_json(*o)}}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
|
|
6
|
+
module Webhookdb::Front
|
|
7
|
+
include Appydays::Configurable
|
|
8
|
+
|
|
9
|
+
configurable(:front) do
|
|
10
|
+
# The api secret is used for webhook verification, the client id and secret are used for OAuth
|
|
11
|
+
setting :api_secret, "front_api_secret"
|
|
12
|
+
setting :client_id, "front_client_id"
|
|
13
|
+
setting :client_secret, "front_client_secret"
|
|
14
|
+
setting :http_timeout, 30
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.oauth_callback_url = Webhookdb.api_url + "/v1/install/front/callback"
|
|
18
|
+
|
|
19
|
+
def self.verify_signature(request)
|
|
20
|
+
request.body.rewind
|
|
21
|
+
body = request.body.read
|
|
22
|
+
base_string = "#{request.env['HTTP_X_FRONT_REQUEST_TIMESTAMP']}:#{body}"
|
|
23
|
+
calculated_signature = OpenSSL::HMAC.base64digest(OpenSSL::Digest.new("sha256"), self.api_secret, base_string)
|
|
24
|
+
return calculated_signature == request.env["HTTP_X_FRONT_SIGNATURE"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.webhook_response(request)
|
|
28
|
+
return Webhookdb::WebhookResponse.error("missing signature") unless request.env["HTTP_X_FRONT_SIGNATURE"]
|
|
29
|
+
|
|
30
|
+
from_front = Webhookdb::Front.verify_signature(request)
|
|
31
|
+
return Webhookdb::WebhookResponse.ok(status: 200) if from_front
|
|
32
|
+
return Webhookdb::WebhookResponse.error("invalid signature")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.initial_verification_request_response(request)
|
|
36
|
+
from_front = self.verify_signature(request)
|
|
37
|
+
if from_front
|
|
38
|
+
return Webhookdb::WebhookResponse.ok(
|
|
39
|
+
json: {challenge: request.env["HTTP_X_FRONT_CHALLENGE"]},
|
|
40
|
+
status: 200,
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
return Webhookdb::WebhookResponse.error("invalid credentials")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.auth_headers(token)
|
|
47
|
+
return {"Authorization" => "Bearer #{token}"}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/security_utils"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Github
|
|
6
|
+
include Appydays::Configurable
|
|
7
|
+
|
|
8
|
+
configurable(:github) do
|
|
9
|
+
setting :http_timeout, 30
|
|
10
|
+
setting :activity_cron_expression, "*/5 * * * *"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.parse_link_header(header)
|
|
14
|
+
return Webhookdb::Shopify.parse_link_header(header)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# see https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
|
18
|
+
def self.verify_webhook(body, hmac_header, webhook_secret)
|
|
19
|
+
calculated_hash = "sha256=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), webhook_secret, body)
|
|
20
|
+
return ActiveSupport::SecurityUtils.secure_compare(calculated_hash, hmac_header)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::GoogleCalendar
|
|
6
|
+
include Appydays::Configurable
|
|
7
|
+
|
|
8
|
+
configurable(:google_calendar) do
|
|
9
|
+
# How many calendars/events should we fetch in a single page?
|
|
10
|
+
# Higher uses slightly more memory but fewer API calls.
|
|
11
|
+
# Max of 2500.
|
|
12
|
+
setting :list_page_size, 2000
|
|
13
|
+
# How many rows should we upsert at a time?
|
|
14
|
+
# Higher is fewer upserts, but can create very large SQL strings,
|
|
15
|
+
# which can have negative performance.
|
|
16
|
+
setting :upsert_page_size, 500
|
|
17
|
+
# How long should watch channels live.
|
|
18
|
+
# Generally use Google's default (one week),
|
|
19
|
+
# but set shorter when testing.
|
|
20
|
+
setting :watch_ttl, 604_800
|
|
21
|
+
setting :http_timeout, 30
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Manual backfilling is not supported on Google Calendar integrations.
|
|
25
|
+
# If a manual backfill is attempted, direct customer to this url.
|
|
26
|
+
DOCUMENTATION_URL = "https://docs.webhookdb.com/guides/google-calendar/"
|
|
27
|
+
|
|
28
|
+
PUSH_NOT_SUPPORTED_SENTINEL_WATCH_ID = "ech-push-not-supported-for-requested-resource"
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "platform-api"
|
|
4
|
+
|
|
5
|
+
require "webhookdb"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Heroku
|
|
8
|
+
include Appydays::Configurable
|
|
9
|
+
|
|
10
|
+
configurable(:heroku) do
|
|
11
|
+
setting :oauth_id, "", key: "WEBHOOKDB_HEROKU_OAUTH_ID"
|
|
12
|
+
setting :oauth_token, "", key: "WEBHOOKDB_HEROKU_OAUTH_TOKEN"
|
|
13
|
+
setting :app_name, "", key: "HEROKU_APP_NAME"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.client
|
|
17
|
+
raise "No heroku:oauth_token configured" if self.oauth_token.blank?
|
|
18
|
+
@client ||= PlatformAPI.connect_oauth(self.oauth_token)
|
|
19
|
+
return @client
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httparty"
|
|
4
|
+
|
|
5
|
+
require "appydays/loggable/httparty_formatter"
|
|
6
|
+
|
|
7
|
+
module Webhookdb::Http
|
|
8
|
+
# Error raised when some API has rate limited us.
|
|
9
|
+
class BaseError < StandardError; end
|
|
10
|
+
|
|
11
|
+
class Error < BaseError
|
|
12
|
+
attr_reader :response, :body, :uri, :status, :http_method
|
|
13
|
+
|
|
14
|
+
def initialize(response, msg=nil)
|
|
15
|
+
@response = response
|
|
16
|
+
@body = response.body
|
|
17
|
+
@headers = response.headers.to_h
|
|
18
|
+
@status = response.code
|
|
19
|
+
@uri = response.request.last_uri.dup
|
|
20
|
+
if @uri.query.present?
|
|
21
|
+
cleaned_params = CGI.parse(@uri.query).map { |k, v| k.include?("secret") ? [k, ".snip."] : [k, v] }
|
|
22
|
+
@uri.query = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER.call(cleaned_params)
|
|
23
|
+
end
|
|
24
|
+
@http_method = response.request.http_method::METHOD
|
|
25
|
+
super(msg || self.to_s)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
return "HttpError(status: #{self.status}, method: #{self.http_method}, uri: #{self.uri}, body: #{self.body})"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
alias inspect to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.user_agent
|
|
36
|
+
return Webhookdb.http_user_agent unless Webhookdb.http_user_agent.blank?
|
|
37
|
+
return "WebhookDB/#{Webhookdb::RELEASE} https://webhookdb.com #{Webhookdb::RELEASE_CREATED_AT}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.extract_url_auth(url)
|
|
41
|
+
parsed_uri = URI(url)
|
|
42
|
+
if parsed_uri.userinfo.present?
|
|
43
|
+
auth_params = {
|
|
44
|
+
username: URI.decode_www_form_component(parsed_uri.user || ""),
|
|
45
|
+
password: URI.decode_www_form_component(parsed_uri.password || ""),
|
|
46
|
+
}
|
|
47
|
+
parsed_uri.user = parsed_uri.password = nil
|
|
48
|
+
cleaned_url = parsed_uri.to_s
|
|
49
|
+
return cleaned_url, auth_params
|
|
50
|
+
end
|
|
51
|
+
return url, nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.check!(response, **options)
|
|
55
|
+
# All oks are ok
|
|
56
|
+
return if response.code < 300
|
|
57
|
+
# We expect 300s if we aren't following redirects
|
|
58
|
+
return if response.code < 400 && !options[:follow_redirects]
|
|
59
|
+
# Raise for 400s, or 300s if we were meant to follow redirects
|
|
60
|
+
raise Error, response
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.get(url, query={}, **options, &)
|
|
64
|
+
self._setup_required_args(options)
|
|
65
|
+
opts = {query:, headers: {}}.merge(**options)
|
|
66
|
+
opts[:headers]["User-Agent"] = self.user_agent
|
|
67
|
+
# See https://github.com/jnunemaker/httparty/issues/784#issuecomment-1585714745
|
|
68
|
+
# I *think* this should be safe to always use.
|
|
69
|
+
opts[:headers]["Connection"] ||= "keep-alive"
|
|
70
|
+
r = HTTParty.get(url, **opts, &)
|
|
71
|
+
self.check!(r, **opts)
|
|
72
|
+
return r
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.post(url, body={}, headers: {}, method: nil, check: true, **options, &)
|
|
76
|
+
self._setup_required_args(options)
|
|
77
|
+
headers["Content-Type"] ||= "application/json"
|
|
78
|
+
headers["User-Agent"] = self.user_agent
|
|
79
|
+
body = body.to_json if !body.is_a?(String) && headers["Content-Type"].include?("json")
|
|
80
|
+
opts = {body:, headers:}.merge(**options)
|
|
81
|
+
r = HTTParty.send(method || :post, url, **opts, &)
|
|
82
|
+
self.check!(r, **options) if check
|
|
83
|
+
return r
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self._setup_required_args(options)
|
|
87
|
+
raise ArgumentError, "must pass :timeout keyword" unless options.key?(:timeout)
|
|
88
|
+
|
|
89
|
+
raise ArgumentError, "must pass :logger keyword" unless options.key?(:logger)
|
|
90
|
+
options[:log_format] = :appydays
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Convenience wrapper around Down that handles gzip.
|
|
94
|
+
# @return Array<Down::ChunkedIO, IO> Tuple
|
|
95
|
+
def self.chunked_download(request_url, rewindable: false, **down_kw)
|
|
96
|
+
io = Down::NetHttp.open(request_url, rewindable:, **down_kw)
|
|
97
|
+
if io.data[:headers].fetch("Content-Encoding", "").include?("gzip")
|
|
98
|
+
# If the response is gzipped, Down doesn't handle it properly.
|
|
99
|
+
# Wrap it with gzip reader, and force the encoding to binary
|
|
100
|
+
# the server may send back a header like Content-Type: text/plain; UTF-8,
|
|
101
|
+
# so each line Down yields via #gets will have force_encoding('utf-8').
|
|
102
|
+
# https://github.com/janko/down/issues/87
|
|
103
|
+
io.instance_variable_set(:@encoding, "binary")
|
|
104
|
+
io = Zlib::GzipReader.wrap(io)
|
|
105
|
+
end
|
|
106
|
+
return io
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.gzipped?(string)
|
|
110
|
+
return false if string.length < 3
|
|
111
|
+
b = string[..2].bytes
|
|
112
|
+
return b[0] == 0x1f && b[1] == 0x8b
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Icalendar
|
|
6
|
+
# Manual backfilling is not supported on iCalendar integrations.
|
|
7
|
+
# If a manual backfill is attempted, direct customer to this url.
|
|
8
|
+
DOCUMENTATION_URL = "https://docs.webhookdb.com/guides/icalendar/"
|
|
9
|
+
|
|
10
|
+
include Appydays::Configurable
|
|
11
|
+
|
|
12
|
+
configurable(:icalendar) do
|
|
13
|
+
# Do not store events older then this when syncing recurring events.
|
|
14
|
+
# Many icalendar feeds are misconfigured and this prevents enumerating 2000+ years of recurrence.
|
|
15
|
+
setting :oldest_recurring_event, "1990-01-01", convert: ->(s) { Date.parse(s) }
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/webhookdb/id.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Id
|
|
6
|
+
ID_BYTES = 16
|
|
7
|
+
|
|
8
|
+
def self.new_opaque_id(prefix)
|
|
9
|
+
b36 = self.rand_enc(ID_BYTES)
|
|
10
|
+
return "#{prefix}_#{b36}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.rand_enc(blen)
|
|
14
|
+
b = SecureRandom.bytes(blen)
|
|
15
|
+
return Digest.hexencode(b).to_i(16).to_s(36)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
|
|
5
|
+
# Support idempotent operations.
|
|
6
|
+
# This is very useful when
|
|
7
|
+
# 1) protecting the API against requests dispatched multiple times,
|
|
8
|
+
# as browsers are liable to do, and
|
|
9
|
+
# 2) designing parts of a system so they can be used idempotently, especially async jobs.
|
|
10
|
+
# This ensures an event can be republished if a job fails, but jobs that worked won't be re-run.
|
|
11
|
+
#
|
|
12
|
+
# In general, you do not use Idempotency instances directly;
|
|
13
|
+
# instead, you will use once_ever and every.
|
|
14
|
+
# For example, to only send a welcome email once:
|
|
15
|
+
#
|
|
16
|
+
# Webhookdb::Idempotency.once_ever.under_key("welcome-email-#{customer.id}") { send_welcome_email(customer) }
|
|
17
|
+
#
|
|
18
|
+
# Similarly, to prevent an action email from going out multiple times in a short period accidentally:
|
|
19
|
+
#
|
|
20
|
+
# Webhookdb::Idempotency.every(1.hour).under_key("new-order-#{order.id}") { send_new_order_email(order) }
|
|
21
|
+
#
|
|
22
|
+
# Note that idempotency cannot be executed while already in a transaction.
|
|
23
|
+
# If it were, the unique row would not be visible to other transactions.
|
|
24
|
+
# So the new row must be committed, then the idempotency evaluated (and the callback potentially run).
|
|
25
|
+
# To disable this check, set 'Postgres.unsafe_skip_transaction_check' to true,
|
|
26
|
+
# usually using the :no_transaction_check spec metadata.
|
|
27
|
+
#
|
|
28
|
+
class Webhookdb::Idempotency < Webhookdb::Postgres::Model(:idempotencies)
|
|
29
|
+
extend Webhookdb::MethodUtilities
|
|
30
|
+
|
|
31
|
+
NOOP = :skipped
|
|
32
|
+
|
|
33
|
+
# Skip the transaction check. Useful in unit tests. See class docs for details.
|
|
34
|
+
singleton_predicate_accessor :skip_transaction_check
|
|
35
|
+
|
|
36
|
+
def self.once_ever
|
|
37
|
+
idem = self.new
|
|
38
|
+
idem.__once_ever = true
|
|
39
|
+
return idem
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.every(interval)
|
|
43
|
+
idem = self.new
|
|
44
|
+
idem.__every = interval
|
|
45
|
+
return idem
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
attr_accessor :__every, :__once_ever
|
|
49
|
+
|
|
50
|
+
def under_key(key, &block)
|
|
51
|
+
self.key = key
|
|
52
|
+
return self.execute(&block) if block
|
|
53
|
+
return self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def execute
|
|
57
|
+
Webhookdb::Postgres.check_transaction(
|
|
58
|
+
self.db,
|
|
59
|
+
"Cannot use idempotency while already in a transaction, since side effects may not be idempotent",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.class.dataset.insert_conflict.insert(key: self.key)
|
|
63
|
+
self.db.transaction do
|
|
64
|
+
idem = Webhookdb::Idempotency[key: self.key].lock!
|
|
65
|
+
if idem.last_run.nil?
|
|
66
|
+
result = yield()
|
|
67
|
+
idem.update(last_run: Time.now)
|
|
68
|
+
return result
|
|
69
|
+
end
|
|
70
|
+
return NOOP if self.__once_ever
|
|
71
|
+
return NOOP if Time.now < (idem.last_run + self.__every)
|
|
72
|
+
result = yield()
|
|
73
|
+
idem.update(last_run: Time.now)
|
|
74
|
+
return result
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Table: idempotencies
|
|
80
|
+
# -------------------------------------------------------------------------------------
|
|
81
|
+
# Columns:
|
|
82
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
83
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
84
|
+
# updated_at | timestamp with time zone |
|
|
85
|
+
# last_run | timestamp with time zone |
|
|
86
|
+
# key | text |
|
|
87
|
+
# Indexes:
|
|
88
|
+
# idempotencies_pkey | PRIMARY KEY btree (id)
|
|
89
|
+
# idempotencies_key_key | UNIQUE btree (key)
|
|
90
|
+
# -------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::Increase
|
|
4
|
+
extend Webhookdb::MethodUtilities
|
|
5
|
+
include Appydays::Configurable
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
|
|
8
|
+
configurable(:increase) do
|
|
9
|
+
setting :http_timeout, 30
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.webhook_response(request, webhook_secret)
|
|
13
|
+
http_signature = request.env["HTTP_X_BANK_WEBHOOK_SIGNATURE"]
|
|
14
|
+
|
|
15
|
+
return Webhookdb::WebhookResponse.error("missing hmac") if http_signature.nil?
|
|
16
|
+
|
|
17
|
+
request.body.rewind
|
|
18
|
+
request_data = request.body.read
|
|
19
|
+
|
|
20
|
+
computed_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), webhook_secret, request_data)
|
|
21
|
+
|
|
22
|
+
if http_signature != "sha256=" + computed_signature
|
|
23
|
+
# Invalid signature
|
|
24
|
+
self.logger.warn "increase signature verification error"
|
|
25
|
+
return Webhookdb::WebhookResponse.error("invalid hmac")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
return Webhookdb::WebhookResponse.ok
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# this helper function finds the relevant object data and helps us avoid repeated code
|
|
32
|
+
def self.find_desired_object_data(body)
|
|
33
|
+
return body.fetch("data", body)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# this function interprets webhook contents to assist with filtering webhooks by object type in our increase services
|
|
37
|
+
def self.contains_desired_object(webhook_body, desired_object_name)
|
|
38
|
+
object_of_interest = self.find_desired_object_data(webhook_body)
|
|
39
|
+
object_id = object_of_interest["id"]
|
|
40
|
+
return object_id.include?(desired_object_name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Intercom
|
|
6
|
+
include Appydays::Configurable
|
|
7
|
+
|
|
8
|
+
configurable(:intercom) do
|
|
9
|
+
setting :client_id, "whdb_intercom_client_id", key: "INTERCOM_CLIENT_ID"
|
|
10
|
+
setting :client_secret, "whdb_intercom_client_secret", key: "INTERCOM_CLIENT_SECRET"
|
|
11
|
+
setting :http_timeout, 30
|
|
12
|
+
setting :page_size, 20
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.verify_webhook(data, hmac_header)
|
|
16
|
+
calculated_hmac = "sha1=#{OpenSSL::HMAC.hexdigest('SHA1', self.client_secret, data)}"
|
|
17
|
+
return ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.auth_headers(token)
|
|
21
|
+
return {"Intercom-Version" => "2.9", "Authorization" => "Bearer #{token}"}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "amigo/retry"
|
|
4
|
+
require "amigo/queue_backoff_job"
|
|
5
|
+
require "amigo/durable_job"
|
|
6
|
+
require "sidekiq"
|
|
7
|
+
|
|
8
|
+
# Use this to verify the behavior of durable jobs:
|
|
9
|
+
#
|
|
10
|
+
# - Ensure DISABLE_DURABLE_JOBS_POLL env var is set.
|
|
11
|
+
# - `make run` and then `Webhookdb::Async.open_web` to go to Sidekiq web UI.
|
|
12
|
+
# - `make run-workers`, and copy the PID.
|
|
13
|
+
# - From pry: `Webhookdb::Jobs::DurableSleeper.setup_test_run(30)`
|
|
14
|
+
# - Jobs are put into the queue and the workers will be 'running' (sleeping).
|
|
15
|
+
# - Wait for some jobs to be done sleeping.
|
|
16
|
+
# - `pkill -9 <pid>`, kills Sidekiq without cleanup.
|
|
17
|
+
# - `Webhookdb::Jobs::DurableSleeper.print_status` will print
|
|
18
|
+
# the queue size. It would be '10' if you started with 30 jobs,
|
|
19
|
+
# 10 processed, and the worker was killed while 10 more were processing
|
|
20
|
+
# (leaving 10 unprocessed jobs in the queue).
|
|
21
|
+
# It will also print dead jobs, which will be 0.
|
|
22
|
+
# It will also print processed jobs, will be be 10.
|
|
23
|
+
# - Restart workers with `make run-workers`.
|
|
24
|
+
# - The next 10 workers will run (queue). `print_status` returns 0 for queue and dead size,
|
|
25
|
+
# and 20 for jobs processed.
|
|
26
|
+
# - Run `Amigo::DurableJob.poll_jobs`.
|
|
27
|
+
# - Go to the web UI's Dead jobs. Observe 10 jobs are there. `print_status` also shows 10 jobs as dead.
|
|
28
|
+
# - Retry those jobs.
|
|
29
|
+
# - `print_status` shows 0 dead and 30 processed jobs.
|
|
30
|
+
#
|
|
31
|
+
class Webhookdb::Jobs::DurableSleeper
|
|
32
|
+
include Sidekiq::Job
|
|
33
|
+
include Amigo::DurableJob
|
|
34
|
+
|
|
35
|
+
MUX = Mutex.new
|
|
36
|
+
COUNTER_FILE = ".durable-sleeper-counter"
|
|
37
|
+
|
|
38
|
+
def self.heartbeat_extension
|
|
39
|
+
return 20.seconds
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def perform(duration=5)
|
|
43
|
+
self.logger.info("sleeping")
|
|
44
|
+
sleep(duration)
|
|
45
|
+
MUX.synchronize do
|
|
46
|
+
done = File.read(COUNTER_FILE).to_i
|
|
47
|
+
done += 1
|
|
48
|
+
File.write(COUNTER_FILE, done.to_s)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.setup_test_run(count=30)
|
|
53
|
+
File.write(COUNTER_FILE, "0")
|
|
54
|
+
count.times { Webhookdb::Jobs::DurableSleeper.perform_async }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.print_status
|
|
58
|
+
done = File.read(COUNTER_FILE).to_i
|
|
59
|
+
puts "Queue Size: #{Sidekiq::Queue.new.size}"
|
|
60
|
+
puts "Processed: #{done}"
|
|
61
|
+
puts "Dead Set: #{Sidekiq::DeadSet.new.size}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Use this and BackoffShouldBeRun to test the behavior of BackoffJob.
|
|
66
|
+
#
|
|
67
|
+
# First, fill up the 'netout' queue with a ton of these slow jobs:
|
|
68
|
+
# From pry: `Webhookdb::Async.require_jobs; 500.times { Webhookdb::Jobs::BackoffShouldBeRescheduled.perform_async }`
|
|
69
|
+
#
|
|
70
|
+
# Then, fill up the other queues with fast jobs:
|
|
71
|
+
# `1000.times { Webhookdb::Jobs::BackoffShouldRun.perform_async }`
|
|
72
|
+
#
|
|
73
|
+
# Then go to http://localhost:18001/sidekiq (user/pass) to check the latency.
|
|
74
|
+
# The netout queue should get slow,
|
|
75
|
+
# but the other queues should not build up much of a backlog.
|
|
76
|
+
class Webhookdb::Jobs::BackoffShouldBeRescheduled
|
|
77
|
+
include Sidekiq::Job
|
|
78
|
+
include Amigo::DurableJob # Uncomment to verify performance with durable jobs, which hit the DB.
|
|
79
|
+
include Amigo::QueueBackoffJob
|
|
80
|
+
|
|
81
|
+
sidekiq_options queue: "netout"
|
|
82
|
+
|
|
83
|
+
def perform(duration=3)
|
|
84
|
+
sleep(duration)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Webhookdb::Jobs::BackoffShouldRun
|
|
89
|
+
include Sidekiq::Job
|
|
90
|
+
|
|
91
|
+
def perform(duration=0.1)
|
|
92
|
+
sleep(duration)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class Webhookdb::Jobs::RetryChecker
|
|
97
|
+
include Sidekiq::Job
|
|
98
|
+
|
|
99
|
+
def perform(action, interval, attempts)
|
|
100
|
+
case action
|
|
101
|
+
when "retry"
|
|
102
|
+
raise Amigo::Retry::Retry, interval
|
|
103
|
+
when "die"
|
|
104
|
+
raise Amigo::Retry::Die
|
|
105
|
+
else
|
|
106
|
+
raise Amigo::Retry::Die.new(attempts, interval)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class Webhookdb::Jobs::Erroring
|
|
112
|
+
include Sidekiq::Job
|
|
113
|
+
|
|
114
|
+
def perform(succeed: false)
|
|
115
|
+
return if succeed
|
|
116
|
+
raise "erroring as asked!"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "amigo/queue_backoff_job"
|
|
4
|
+
require "amigo/durable_job"
|
|
5
|
+
require "webhookdb/async/job"
|
|
6
|
+
require "webhookdb/jobs"
|
|
7
|
+
|
|
8
|
+
class Webhookdb::Jobs::Backfill
|
|
9
|
+
extend Webhookdb::Async::Job
|
|
10
|
+
include Amigo::DurableJob
|
|
11
|
+
include Amigo::QueueBackoffJob
|
|
12
|
+
|
|
13
|
+
on "webhookdb.backfilljob.run"
|
|
14
|
+
sidekiq_options queue: "netout"
|
|
15
|
+
|
|
16
|
+
def dependent_queues
|
|
17
|
+
# This is really the lowest-priority job so always defer to other queues.
|
|
18
|
+
return super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _perform(event)
|
|
22
|
+
bfjob = self.lookup_model(Webhookdb::BackfillJob, event.payload)
|
|
23
|
+
sint = bfjob.service_integration
|
|
24
|
+
self.with_log_tags(sint.log_tags.merge(backfill_job_id: bfjob.opaque_id)) do
|
|
25
|
+
if bfjob.finished?
|
|
26
|
+
self.logger.info "skipping_finished_backfill_job"
|
|
27
|
+
else
|
|
28
|
+
sint.replicator.backfill(bfjob)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/async/job"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Jobs::CreateMirrorTable
|
|
6
|
+
extend Webhookdb::Async::Job
|
|
7
|
+
|
|
8
|
+
on "webhookdb.serviceintegration.created"
|
|
9
|
+
sidekiq_options queue: "critical"
|
|
10
|
+
|
|
11
|
+
def _perform(event)
|
|
12
|
+
sint = self.lookup_model(Webhookdb::ServiceIntegration, event)
|
|
13
|
+
self.with_log_tags(sint.log_tags) do
|
|
14
|
+
svc = Webhookdb::Replicator.create(sint)
|
|
15
|
+
svc.create_table(if_not_exists: true)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|