webhookdb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|