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,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/async/job"
|
|
4
|
+
require "stripe"
|
|
5
|
+
require "webhookdb/stripe"
|
|
6
|
+
|
|
7
|
+
# All the 'webhookdb' integrations are internal, so this endpoint is not hardened thoroughly.
|
|
8
|
+
# If we were to open this up or use it on prod, we need to harden it to support
|
|
9
|
+
# more and slower endpoints (at least by fanning out the work).
|
|
10
|
+
class Webhookdb::Jobs::WebhookdbResourceNotifyIntegrations
|
|
11
|
+
extend Webhookdb::Async::Job
|
|
12
|
+
|
|
13
|
+
# As we add more resources, modify this wildcard
|
|
14
|
+
on "webhookdb.customer.created"
|
|
15
|
+
|
|
16
|
+
sidekiq_options queue: "netout"
|
|
17
|
+
|
|
18
|
+
def _perform(event)
|
|
19
|
+
cu = self.lookup_model(Webhookdb::Customer, event)
|
|
20
|
+
Webhookdb::ServiceIntegration.where(service_name: "webhookdb_customer_v1").each do |sint|
|
|
21
|
+
Webhookdb::Http.post(
|
|
22
|
+
sint.replicator.webhook_endpoint,
|
|
23
|
+
cu.values,
|
|
24
|
+
headers: {"Whdb-Secret" => sint.webhook_secret},
|
|
25
|
+
logger: self.logger,
|
|
26
|
+
timeout: nil,
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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::WebhookSubscriptionDeliveryEvent
|
|
9
|
+
include Sidekiq::Worker
|
|
10
|
+
include Amigo::DurableJob
|
|
11
|
+
include Amigo::QueueBackoffJob
|
|
12
|
+
|
|
13
|
+
sidekiq_options queue: "netout"
|
|
14
|
+
|
|
15
|
+
def dependent_queues
|
|
16
|
+
return ["critical"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def perform(delivery_id)
|
|
20
|
+
delivery = Webhookdb::WebhookSubscription::Delivery[delivery_id]
|
|
21
|
+
Webhookdb::Async::JobLogger.with_log_tags(
|
|
22
|
+
webhook_subscription_delivery_id: delivery.id,
|
|
23
|
+
webhook_subscription_id: delivery.webhook_subscription_id,
|
|
24
|
+
organization_key: delivery.webhook_subscription.fetch_organization,
|
|
25
|
+
) do
|
|
26
|
+
delivery.attempt_delivery
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "oj"
|
|
5
|
+
require "active_support/json"
|
|
6
|
+
|
|
7
|
+
module Webhookdb
|
|
8
|
+
# Helpers for JSON encoding and (to a lesser degree) decoding.
|
|
9
|
+
# In general, decoding JSON in Ruby is simple, as long as we
|
|
10
|
+
# don't try and deal with automatically converting types (which we don't).
|
|
11
|
+
#
|
|
12
|
+
# But encoding JSON is complex, like for example encoding a Time.
|
|
13
|
+
# ActiveSupport uses #as_json to convert Ruby types to JSON-native types.
|
|
14
|
+
# In order to use this, we must call Webhookdb::Json.encode,
|
|
15
|
+
# or go through ActiveSupport (which we patch here).
|
|
16
|
+
#
|
|
17
|
+
# That is, using JSON.dump(Time.now) would give you '"1970-01-01 02:00:00 +0200"'
|
|
18
|
+
# but using Webhookdb::Json.encode(Time.now) gives you '"1970-01-01T02:00:00.123+02:00"'
|
|
19
|
+
#
|
|
20
|
+
# Anyway, this is all largely under the hood and handled for us using ActiveSupport,
|
|
21
|
+
# but while we switched Yajl to Oj, we went ahead and added this shim to bypass
|
|
22
|
+
# the worst part of ActiveSupport (its still very tied to Rails needs).
|
|
23
|
+
module Json
|
|
24
|
+
DEFAULT_OPTIONS = {}.freeze
|
|
25
|
+
PRETTY_OPTIONS = {indent: " ", space: " ", object_nl: "\n", array_nl: "\n"}.freeze
|
|
26
|
+
|
|
27
|
+
# Based on ActiveSupport::JSON::Encoding::JSONGemEncoder.
|
|
28
|
+
# Removes the HTML escaping code (which is slow), but otherwise continues to depend
|
|
29
|
+
# on the as_json approach.
|
|
30
|
+
class Encoder
|
|
31
|
+
attr_reader :options
|
|
32
|
+
|
|
33
|
+
def initialize(options=nil)
|
|
34
|
+
@options = options || DEFAULT_OPTIONS
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def encode(value)
|
|
38
|
+
return stringify(jsonify(value.as_json(@options.dup)))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Convert an object into a "JSON-ready" representation composed of
|
|
42
|
+
# primitives like Hash, Array, String, Numeric,
|
|
43
|
+
# and +true+/+false+/+nil+.
|
|
44
|
+
# Recursively calls #as_json to the object to recursively build a
|
|
45
|
+
# fully JSON-ready object.
|
|
46
|
+
#
|
|
47
|
+
# This allows developers to implement #as_json without having to
|
|
48
|
+
# worry about what base types of objects they are allowed to return
|
|
49
|
+
# or having to remember to call #as_json recursively.
|
|
50
|
+
#
|
|
51
|
+
# Note: the +options+ hash passed to +object.to_json+ is only passed
|
|
52
|
+
# to +object.as_json+, not any of this method's recursive +#as_json+
|
|
53
|
+
# calls.
|
|
54
|
+
def jsonify(value)
|
|
55
|
+
case value
|
|
56
|
+
when Rational
|
|
57
|
+
value.to_s
|
|
58
|
+
when String, Numeric, NilClass, TrueClass, FalseClass
|
|
59
|
+
value.as_json
|
|
60
|
+
when Hash
|
|
61
|
+
result = {}
|
|
62
|
+
value.each do |k, v|
|
|
63
|
+
result[jsonify(k)] = jsonify(v)
|
|
64
|
+
end
|
|
65
|
+
result
|
|
66
|
+
when Array
|
|
67
|
+
value.map { |v| jsonify(v) }
|
|
68
|
+
else
|
|
69
|
+
jsonify value.as_json
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stringify(jsonified)
|
|
74
|
+
# If this breaks because we actually need to handle more types,
|
|
75
|
+
# add them to jsonify, like Rational.
|
|
76
|
+
#
|
|
77
|
+
# We can't use `mode: :rails` here since we can't control
|
|
78
|
+
# whether to use html-escaping (we don't want to use it) without using `Oj.optimize_rails`,
|
|
79
|
+
# which causes other problems- we want to use ISO8601 encoding with millsecond precision,
|
|
80
|
+
# but we get JSON-gem-style formatting (even if setting flags to modify ActiveSupport params)
|
|
81
|
+
# when we use `optimize-rails` (but not mode: :rails, since we stringify the time beforehand).
|
|
82
|
+
return ::Oj.dump(jsonified, mode: :strict, **@options)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class << self
|
|
87
|
+
# Dump as compact, standard JSON, using iso8601 for times.
|
|
88
|
+
def encode(value, options=nil)
|
|
89
|
+
Encoder.new(options).encode(value)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Dump as pretty JSON, similar to JSON.pretty_generate but with iso8601 times.
|
|
93
|
+
def pretty_generate(value)
|
|
94
|
+
Webhookdb::Json.encode(value, PRETTY_OPTIONS)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# This stomps the JSON.load, etc., methods, to use the faster (and still compatible) Oj version.
|
|
101
|
+
Oj.mimic_JSON
|
|
102
|
+
|
|
103
|
+
# Use the shim encoder rather than the ActiveSupport one.
|
|
104
|
+
# This mostly handles #to_json calls.
|
|
105
|
+
ActiveSupport::JSON::Encoding.json_encoder = Webhookdb::Json::Encoder
|
|
106
|
+
|
|
107
|
+
# Replace ActiveSupport::JSON.encode so it calls Oj directly.
|
|
108
|
+
# This isn't strictly necessary (since we set json_encoder), but skips an extra method call.
|
|
109
|
+
module ActiveSupport::JSON
|
|
110
|
+
def self.encode(value, options=nil)
|
|
111
|
+
return Webhookdb::Json.encode(value, options)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "liquid"
|
|
4
|
+
|
|
5
|
+
require "webhookdb"
|
|
6
|
+
|
|
7
|
+
# Allow customers to expose variables from a template
|
|
8
|
+
# using custom blocks and registers.
|
|
9
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tag-blocks
|
|
10
|
+
# for more info about blocks.
|
|
11
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#difference-between-assigns-and-registers
|
|
12
|
+
# for info about "registers", which are used as template-render-specific mutable state
|
|
13
|
+
# (so we can mutate it in the tag/block, then inspect the mutated value after-the-fact).
|
|
14
|
+
class Webhookdb::Liquid::Expose < Liquid::Block
|
|
15
|
+
def initialize(tag_name, var_name, options)
|
|
16
|
+
super
|
|
17
|
+
@var_name = var_name.strip.to_sym
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render(context)
|
|
21
|
+
content = super
|
|
22
|
+
context.registers[@var_name] = content
|
|
23
|
+
""
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Liquid::Template.register_tag("expose", Webhookdb::Liquid::Expose)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "liquid"
|
|
4
|
+
require "webhookdb"
|
|
5
|
+
|
|
6
|
+
module Webhookdb::Liquid::Filters
|
|
7
|
+
def humanize(input)
|
|
8
|
+
return input.humanize
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def money(input)
|
|
12
|
+
return input.format
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Liquid::Template.register_filter(Webhookdb::Liquid::Filters)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Liquification provides a to_liquid method that returns a wrapped object,
|
|
4
|
+
# so that any object can be used in a liquid template
|
|
5
|
+
# (normally only things like basic types, and objects with to_liquid,
|
|
6
|
+
# can be used in templates). This allows us to use Liquid filters for rendering
|
|
7
|
+
# custom types, rather than presenters. We prefer filters because
|
|
8
|
+
# it keeps display logic in the view, rather than the backing 'template'
|
|
9
|
+
# having to choose the presenter.
|
|
10
|
+
class Webhookdb::Liquid::Liquification
|
|
11
|
+
def initialize(wrapped)
|
|
12
|
+
@wrapped = wrapped
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def method_missing(m, *, &)
|
|
16
|
+
return @wrapped.respond_to?(m) ? @wrapped.send(m, *, &) : super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def respond_to_missing?(m, include_private: false)
|
|
20
|
+
return super || @wrapped.respond_to?(m)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_liquid
|
|
24
|
+
return self
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb"
|
|
4
|
+
require "liquid"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::Liquid::Partial < Liquid::Include
|
|
7
|
+
def initialize(tag_name, name, options)
|
|
8
|
+
name = "'partials/#{Regexp.last_match(1)}'" if name =~ /['"]([a-z0-9_]+)['"]/
|
|
9
|
+
super(tag_name, name, options)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
Liquid::Template.register_tag("partial", Webhookdb::Liquid::Partial)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::LoggedWebhook::Resilient
|
|
4
|
+
def logger = Webhookdb::LoggedWebhook.logger
|
|
5
|
+
|
|
6
|
+
def database_urls = Webhookdb::LoggedWebhook.available_resilient_database_urls
|
|
7
|
+
|
|
8
|
+
def insert(kwargs)
|
|
9
|
+
return Webhookdb::LoggedWebhook.dataset.insert(kwargs)
|
|
10
|
+
rescue Sequel::DatabaseError => e
|
|
11
|
+
service_integration_opaque_id = kwargs.fetch(:service_integration_opaque_id)
|
|
12
|
+
str_payload = JSON.dump(kwargs)
|
|
13
|
+
self.database_urls.each do |url|
|
|
14
|
+
next unless self.write_to(url, service_integration_opaque_id, str_payload)
|
|
15
|
+
self.logger.warn "resilient_insert_handled", error: e, **self._dburl_log_kwargs(url)
|
|
16
|
+
return true
|
|
17
|
+
end
|
|
18
|
+
self.logger.error "resilient_insert_unhandled", error: e, logged_webhook_kwargs: kwargs
|
|
19
|
+
raise
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def write_to(dburl, service_integration_opaque_id, str_payload)
|
|
23
|
+
tblname = Webhookdb::LoggedWebhook.resilient_table_name
|
|
24
|
+
Sequel.connect(dburl, single_threaded: true) do |db|
|
|
25
|
+
begin
|
|
26
|
+
db.create_table?(tblname.to_sym) do
|
|
27
|
+
primary_key :pk
|
|
28
|
+
text :service_integration_opaque_id
|
|
29
|
+
text :json_payload
|
|
30
|
+
end
|
|
31
|
+
rescue Sequel::UniqueConstraintViolation
|
|
32
|
+
# We cannot avoid this race condition. If needed, we can optimize this, but it's a pain
|
|
33
|
+
# so don't worry about it for now.
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
db.from(tblname).insert(service_integration_opaque_id:, json_payload: str_payload)
|
|
37
|
+
end
|
|
38
|
+
return true
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
self.logger.debug "resilient_insert_failure", error: e, **self._dburl_log_kwargs(dburl)
|
|
41
|
+
return false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _dburl_log_kwargs(dburl)
|
|
45
|
+
u = URI(dburl)
|
|
46
|
+
return {fallback_database_host: u.host, fallback_database_name: u.path}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# - For each (reachable) database:
|
|
50
|
+
# - Select 1 row, with a lock
|
|
51
|
+
# - Replay the webhook
|
|
52
|
+
# - On success, delete the row
|
|
53
|
+
# - On failure, process the next row
|
|
54
|
+
def replay
|
|
55
|
+
tblname = Webhookdb::LoggedWebhook.resilient_table_name
|
|
56
|
+
begin
|
|
57
|
+
Webhookdb::LoggedWebhook.db.execute("SELECT 1=1")
|
|
58
|
+
rescue Sequel::DatabaseError
|
|
59
|
+
self.logger.debug("resilient_replay_primary_db_not_ready")
|
|
60
|
+
return nil
|
|
61
|
+
end
|
|
62
|
+
replayed = 0
|
|
63
|
+
self.database_urls.each do |url|
|
|
64
|
+
Sequel.connect(url) do |rdb|
|
|
65
|
+
has_more = true
|
|
66
|
+
# Keep track of the last pk we've replayed, so we can grab the next available one.
|
|
67
|
+
# Otherwise we can end up spinning on the same one, especially with other threads.
|
|
68
|
+
seen_pk = 0
|
|
69
|
+
while has_more
|
|
70
|
+
# Each row must be processed in a transaction
|
|
71
|
+
rdb.transaction do
|
|
72
|
+
row = rdb.from(tblname).where { pk > seen_pk }.for_update.skip_locked.order(:pk).limit(1).first
|
|
73
|
+
if row.nil?
|
|
74
|
+
has_more = false
|
|
75
|
+
break # The break only works for the transaction
|
|
76
|
+
end
|
|
77
|
+
pk = row.fetch(:pk)
|
|
78
|
+
seen_pk = pk
|
|
79
|
+
payload = JSON.parse(row.fetch(:json_payload))
|
|
80
|
+
# We replay the webhook from a separate job
|
|
81
|
+
# so it can be done idempotently/exclusively.
|
|
82
|
+
lwh = Webhookdb::LoggedWebhook.create(payload)
|
|
83
|
+
lwh.replay_async
|
|
84
|
+
replayed += 1
|
|
85
|
+
rdb.from(tblname).where(pk:).delete
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
return replayed
|
|
90
|
+
rescue Sequel::DatabaseError
|
|
91
|
+
self.logger.debug("resilient_replay_fallback_unavailable", **self._dburl_log_kwargs(url))
|
|
92
|
+
return nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/postgres/model"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::LoggedWebhook < Webhookdb::Postgres::Model(:logged_webhooks)
|
|
8
|
+
include Appydays::Configurable
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
attr_accessor :available_resilient_database_urls
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
configurable(:logged_webhooks) do
|
|
15
|
+
# Space-separated URLs to use for resilient/high availability writes.
|
|
16
|
+
setting :resilient_database_urls, [], convert: ->(s) { s.split.map(&:strip) }
|
|
17
|
+
setting :resilient_database_env_vars, [], convert: ->(s) { s.split.map(&:strip) }
|
|
18
|
+
setting :resilient_table_name, "_logged_webhooks_resilient_writes"
|
|
19
|
+
# Using /replay can send this many webhooks in one request.
|
|
20
|
+
# Making this too high can cause timeouts- the caller may have to make multiple calls instead.
|
|
21
|
+
setting :maximum_replay_interval_hours, 4
|
|
22
|
+
# Webhooks this old cannot be replayed; they are probably truncated anyway.
|
|
23
|
+
setting :maximum_replay_history_hours, 7 * 24
|
|
24
|
+
|
|
25
|
+
after_configured do
|
|
26
|
+
self.available_resilient_database_urls = self.resilient_database_urls.dup
|
|
27
|
+
self.available_resilient_database_urls.concat(self.resilient_database_env_vars.map { |e| ENV.fetch(e, nil) })
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
many_to_one :organization, class: "Webhookdb::Organization"
|
|
32
|
+
|
|
33
|
+
many_to_one :service_integration,
|
|
34
|
+
class: "Webhookdb::ServiceIntegration",
|
|
35
|
+
key: :service_integration_opaque_id,
|
|
36
|
+
primary_key: :opaque_id
|
|
37
|
+
|
|
38
|
+
DELETE_UNOWNED = 14.days
|
|
39
|
+
DELETE_SUCCESSES = 90.days
|
|
40
|
+
TRUNCATE_SUCCESSES = 7.days
|
|
41
|
+
DELETE_FAILURES = 90.days
|
|
42
|
+
TRUNCATE_FAILURES = 30.days
|
|
43
|
+
# When we retry a request, set this so we know not to re-log it.
|
|
44
|
+
RETRY_HEADER = "Whdb-Logged-Webhook-Retry"
|
|
45
|
+
# When we retry a request, these headers
|
|
46
|
+
# must come from the Ruby client, NOT the original request.
|
|
47
|
+
NONOVERRIDABLE_HEADERS = [
|
|
48
|
+
"Accept-Encoding",
|
|
49
|
+
"Accept",
|
|
50
|
+
"Host",
|
|
51
|
+
"Version",
|
|
52
|
+
].to_set
|
|
53
|
+
# These headers have been added by Heroku/our web host,
|
|
54
|
+
# so should not be part of the retry.
|
|
55
|
+
WEBHOST_HEADERS = [
|
|
56
|
+
"Connection",
|
|
57
|
+
"Connect-Time",
|
|
58
|
+
"X-Request-Id",
|
|
59
|
+
"X-Forwarded-For",
|
|
60
|
+
"X-Request-Start",
|
|
61
|
+
"Total-Route-Time",
|
|
62
|
+
"X-Forwarded-Port",
|
|
63
|
+
"X-Forwarded-Proto",
|
|
64
|
+
"Via",
|
|
65
|
+
].to_set
|
|
66
|
+
|
|
67
|
+
# Trim logged webhooks to keep this table to a reasonable size.
|
|
68
|
+
# The current trim algorithm and rationale is:
|
|
69
|
+
#
|
|
70
|
+
# - Logs that belong to inserts that were not part of an org are for our internal use only.
|
|
71
|
+
# They usually indicate an integration that was misconfigured, or is for an org that doesn't exist.
|
|
72
|
+
# We keep these around for 2 weeks (they are always errors since they have no org).
|
|
73
|
+
# Ideally we investigate and remove them before that.
|
|
74
|
+
# We may need to 'block' certain opaque ids from being logged in the future,
|
|
75
|
+
# if for example we cannot get a client to turn off a misconfigured webhook.
|
|
76
|
+
# - Successful webhooks get their contents (request body and headers)
|
|
77
|
+
# _truncated_ after 7 days (but the webhook row remains).
|
|
78
|
+
# Usually we don't need to worry about these so in theory we can avoid logging verbose info at all.
|
|
79
|
+
# - Successful webhooks are deleted entirely after 90 days.
|
|
80
|
+
# Truncated webhooks are useful for statistics,
|
|
81
|
+
# but we can remove them earlier in the future.
|
|
82
|
+
# - Failed webhooks get their contents truncated after 30 days,
|
|
83
|
+
# but the webhook row remains. We have a longer truncation date
|
|
84
|
+
# so we have more time to investigate.
|
|
85
|
+
# - Error webhooks are deleted entirely after 90 days.
|
|
86
|
+
def self.trim(now: Time.now)
|
|
87
|
+
owned = self.exclude(organization_id: nil)
|
|
88
|
+
unowned = self.where(organization_id: nil)
|
|
89
|
+
successes = owned.where { response_status < 400 }
|
|
90
|
+
failures = owned.where { response_status >= 400 }
|
|
91
|
+
# Delete old unowned
|
|
92
|
+
unowned.where { inserted_at < now - DELETE_UNOWNED }.delete
|
|
93
|
+
# Delete successes first so they don't have to be truncated
|
|
94
|
+
successes.where { inserted_at < now - DELETE_SUCCESSES }.delete
|
|
95
|
+
self.truncate_dataset(successes.where { inserted_at < now - TRUNCATE_SUCCESSES })
|
|
96
|
+
# Delete failures
|
|
97
|
+
failures.where { inserted_at < now - DELETE_FAILURES }.delete
|
|
98
|
+
self.truncate_dataset(failures.where { inserted_at < now - TRUNCATE_FAILURES })
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Send instances back in 'through the front door' of this API.
|
|
102
|
+
# Return is a partition of [logs with 2xx responses, others].
|
|
103
|
+
# Generally you can safely call `truncate_logs(result[0])`,
|
|
104
|
+
# or pass in (truncate_successful: true).
|
|
105
|
+
def self.retry_logs(instances, truncate_successful: false)
|
|
106
|
+
successes, failures = instances.partition do |lw|
|
|
107
|
+
uri = URI(Webhookdb.api_url + lw.request_path)
|
|
108
|
+
req = Net::HTTP::Post.new(uri.path, {"Content-Type" => "application/json"})
|
|
109
|
+
req.body = lw.request_body
|
|
110
|
+
# This is going to have these headers:
|
|
111
|
+
# ["content-type", "accept-encoding", "accept", "user-agent", "host"]
|
|
112
|
+
# We want to keep all of these, except if user-agent or content-type were set
|
|
113
|
+
# in the original request; then we want to use those.
|
|
114
|
+
# Additionally, there are a whole set of headers we'll find on our webserver
|
|
115
|
+
# that are added by our web platform, which we do NOT want to include.
|
|
116
|
+
lw.request_headers.each do |k, v|
|
|
117
|
+
next if Webhookdb::LoggedWebhook::WEBHOST_HEADERS.include?(k)
|
|
118
|
+
next if Webhookdb::LoggedWebhook::NONOVERRIDABLE_HEADERS.include?(k)
|
|
119
|
+
req[k] = v
|
|
120
|
+
end
|
|
121
|
+
req[Webhookdb::LoggedWebhook::RETRY_HEADER] = lw.id
|
|
122
|
+
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
123
|
+
http.request(req)
|
|
124
|
+
end
|
|
125
|
+
resp.code.to_i < 400
|
|
126
|
+
end
|
|
127
|
+
self.truncate_logs(*successes) if truncate_successful
|
|
128
|
+
return successes, failures
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def retry_one(truncate_successful: false)
|
|
132
|
+
_, bad = self.class.retry_logs([self], truncate_successful:)
|
|
133
|
+
return bad.empty?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def replay_async
|
|
137
|
+
return self.publish_immediate("replay", self.id)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Truncate the logs id'ed by the given instances.
|
|
141
|
+
# Instances are NOT modified; you need to .refresh to see truncated values.
|
|
142
|
+
def self.truncate_logs(*instances)
|
|
143
|
+
ds = self.where(id: instances.map(&:id))
|
|
144
|
+
return self.truncate_dataset(ds)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.truncate_dataset(ds)
|
|
148
|
+
return ds.update(request_body: "", request_headers: "{}", truncated_at: Time.now)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def truncated?
|
|
152
|
+
return self.truncated_at ? true : false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Insert the logged webhook, and fall back to inserting into the configured
|
|
156
|
+
# available_resilient_database_urls. If none are inserted successfully, raise the error;
|
|
157
|
+
# otherwise, swallow the insert error and more on.
|
|
158
|
+
#
|
|
159
|
+
# Note that these resilient inserts are MUCH slower than normal inserts;
|
|
160
|
+
# they require a separate database connection, CREATE TABLE call, etc.
|
|
161
|
+
# But it's a reasonable way to handle when the database is down.
|
|
162
|
+
def self.resilient_insert(**kwargs)
|
|
163
|
+
Resilient.new.insert(kwargs)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Replay and delete all rows in the resilient database tables.
|
|
167
|
+
def self.resilient_replay
|
|
168
|
+
Resilient.new.replay
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
require "webhookdb/logged_webhook/resilient"
|
|
173
|
+
|
|
174
|
+
# Table: logged_webhooks
|
|
175
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
176
|
+
# Columns:
|
|
177
|
+
# id | bigint | PRIMARY KEY DEFAULT nextval('logged_webhooks_id_seq'::regclass)
|
|
178
|
+
# inserted_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
179
|
+
# truncated_at | timestamp with time zone |
|
|
180
|
+
# request_body | text | NOT NULL
|
|
181
|
+
# request_headers | jsonb | NOT NULL
|
|
182
|
+
# response_status | smallint | NOT NULL
|
|
183
|
+
# service_integration_opaque_id | text | NOT NULL
|
|
184
|
+
# organization_id | integer |
|
|
185
|
+
# request_method | text | NOT NULL
|
|
186
|
+
# request_path | text | NOT NULL
|
|
187
|
+
# Indexes:
|
|
188
|
+
# logged_webhooks_pkey | PRIMARY KEY btree (id)
|
|
189
|
+
# logged_webhooks_inserted_at_index | btree (inserted_at)
|
|
190
|
+
# logged_webhooks_organization_id_index | btree (organization_id)
|
|
191
|
+
# logged_webhooks_service_integration_opaque_id_index | btree (service_integration_opaque_id)
|
|
192
|
+
# Foreign key constraints:
|
|
193
|
+
# logged_webhooks_organization_id_fkey | (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
|
|
194
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/message"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Message::Body < Webhookdb::Postgres::Model(:message_bodies)
|
|
8
|
+
plugin :timestamps
|
|
9
|
+
|
|
10
|
+
many_to_one :delivery, class: "Webhookdb::Message::Delivery"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Table: message_bodies
|
|
14
|
+
# ----------------------------------------------------------------------------------------------------
|
|
15
|
+
# Columns:
|
|
16
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
17
|
+
# content | text | NOT NULL
|
|
18
|
+
# mediatype | text | NOT NULL
|
|
19
|
+
# delivery_id | integer | NOT NULL
|
|
20
|
+
# Indexes:
|
|
21
|
+
# message_bodies_pkey | PRIMARY KEY btree (id)
|
|
22
|
+
# message_bodies_delivery_id_index | btree (delivery_id)
|
|
23
|
+
# Foreign key constraints:
|
|
24
|
+
# message_bodies_delivery_id_fkey | (delivery_id) REFERENCES message_deliveries(id) ON DELETE CASCADE
|
|
25
|
+
# ----------------------------------------------------------------------------------------------------
|