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,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "amigo/job"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/async"
|
|
6
|
+
|
|
7
|
+
module Webhookdb::Async::Job
|
|
8
|
+
def self.extended(cls)
|
|
9
|
+
cls.extend Amigo::Job
|
|
10
|
+
cls.include(InstanceMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
def with_log_tags(tags, &)
|
|
15
|
+
Webhookdb::Async::JobLogger.with_log_tags(tags, &)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/loggable/sidekiq_job_logger"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Async::JobLogger < Appydays::Loggable::SidekiqJobLogger
|
|
6
|
+
protected def slow_job_seconds
|
|
7
|
+
return Webhookdb::Async.slow_job_seconds
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.durable_job_failure_notifier(job)
|
|
11
|
+
# See https://github.com/sidekiq/sidekiq/wiki/Job-Format#activejob-middleware-format
|
|
12
|
+
# for job format.
|
|
13
|
+
job = job.dup
|
|
14
|
+
# These fields always exist.
|
|
15
|
+
jargs = job.delete("args")
|
|
16
|
+
jcls = job.delete("class")
|
|
17
|
+
jid = job.delete("jid")
|
|
18
|
+
jq = job.delete("queue")
|
|
19
|
+
jcreated = job.delete("created_at")
|
|
20
|
+
safe_fields = [
|
|
21
|
+
{title: "Job ID", value: jid, short: true},
|
|
22
|
+
{title: "Job Class", value: "`#{jcls}`", short: true},
|
|
23
|
+
{title: "Args", value: "```#{jargs.to_json}```"},
|
|
24
|
+
{title: "Queue", value: jq, short: true},
|
|
25
|
+
{title: "Created At", value: self._ts("created_at", jcreated), short: true},
|
|
26
|
+
]
|
|
27
|
+
# The remaining fields can be added dynamically.
|
|
28
|
+
other_fields = job.compact.
|
|
29
|
+
map { |k, v| {title: k.humanize, value: self._ts(k, v), short: true} }
|
|
30
|
+
Webhookdb::DeveloperAlert.new(
|
|
31
|
+
subsystem: "Job Died",
|
|
32
|
+
emoji: ":zombie:",
|
|
33
|
+
fallback: "Job #{jcls}[#{jid}][#{jargs}] moved to DeadSet",
|
|
34
|
+
fields: safe_fields + other_fields,
|
|
35
|
+
).emit
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self._ts(k, v)
|
|
39
|
+
return nil if v.nil?
|
|
40
|
+
return v unless k.end_with?("_at")
|
|
41
|
+
return Time.at(v) if v.is_a?(Numeric)
|
|
42
|
+
return Time.parse(v) if v.is_a?(String)
|
|
43
|
+
return v
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "amigo/scheduled_job"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/async"
|
|
6
|
+
|
|
7
|
+
module Webhookdb::Async::ScheduledJob
|
|
8
|
+
def self.extended(cls)
|
|
9
|
+
cls.extend Amigo::ScheduledJob
|
|
10
|
+
cls.include(InstanceMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
def with_log_tags(tags, &)
|
|
15
|
+
Webhookdb::Async::JobLogger.with_log_tags(tags, &)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "amigo/retry"
|
|
4
|
+
require "amigo/durable_job"
|
|
5
|
+
require "amigo/rate_limited_error_handler"
|
|
6
|
+
require "appydays/configurable"
|
|
7
|
+
require "appydays/loggable"
|
|
8
|
+
require "sentry-sidekiq"
|
|
9
|
+
require "sidekiq"
|
|
10
|
+
require "sidekiq-cron"
|
|
11
|
+
|
|
12
|
+
Sidekiq.strict_args!
|
|
13
|
+
|
|
14
|
+
require "webhookdb"
|
|
15
|
+
|
|
16
|
+
module Webhookdb::Async
|
|
17
|
+
include Appydays::Configurable
|
|
18
|
+
include Appydays::Loggable
|
|
19
|
+
extend Webhookdb::MethodUtilities
|
|
20
|
+
|
|
21
|
+
require "webhookdb/async/job_logger"
|
|
22
|
+
require "webhookdb/async/audit_logger"
|
|
23
|
+
|
|
24
|
+
configurable(:async) do
|
|
25
|
+
# The number of (Float) seconds that should be considered "slow" for a job.
|
|
26
|
+
# Jobs that take longer than this amount of time will be logged
|
|
27
|
+
# at `warn` level.
|
|
28
|
+
setting :slow_job_seconds, 1.0
|
|
29
|
+
|
|
30
|
+
setting :sidekiq_redis_url, "redis://localhost:6379/0", key: "REDIS_URL"
|
|
31
|
+
setting :sidekiq_redis_provider, ""
|
|
32
|
+
# For sidekiq web UI. Randomize a default so they will only be useful if set.
|
|
33
|
+
setting :web_username, SecureRandom.hex(8)
|
|
34
|
+
setting :web_password, SecureRandom.hex(8)
|
|
35
|
+
|
|
36
|
+
setting :error_reporting_sample_rate, 0.1
|
|
37
|
+
setting :error_reporting_ttl, 120
|
|
38
|
+
|
|
39
|
+
after_configured do
|
|
40
|
+
# Very hard to to test this, so it's not tested.
|
|
41
|
+
url = self.sidekiq_redis_provider.present? ? ENV.fetch(self.sidekiq_redis_provider, nil) : self.sidekiq_redis_url
|
|
42
|
+
redis_params = {url:}
|
|
43
|
+
if url.start_with?("rediss:") && ENV["HEROKU_APP_ID"]
|
|
44
|
+
# rediss: schema is Redis with SSL. They use self-signed certs, so we have to turn off SSL verification.
|
|
45
|
+
# There is not a clear KB on this, you have to piece it together from Heroku and Sidekiq docs.
|
|
46
|
+
redis_params[:ssl_params] = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
|
|
47
|
+
end
|
|
48
|
+
Sidekiq.configure_server do |config|
|
|
49
|
+
config.redis = redis_params
|
|
50
|
+
config.options[:job_logger] = Webhookdb::Async::JobLogger
|
|
51
|
+
# We do NOT want the unstructured default error handler
|
|
52
|
+
config.error_handlers.replace([Webhookdb::Async::JobLogger.method(:error_handler)])
|
|
53
|
+
# We must then replace the otherwise-automatically-added sentry middleware
|
|
54
|
+
config.error_handlers << Amigo::RateLimitedErrorHandler.new(
|
|
55
|
+
Sentry::Sidekiq::ErrorHandler.new,
|
|
56
|
+
sample_rate: self.error_reporting_sample_rate,
|
|
57
|
+
ttl: self.error_reporting_ttl,
|
|
58
|
+
)
|
|
59
|
+
config.death_handlers << Webhookdb::Async::JobLogger.method(:death_handler)
|
|
60
|
+
config.server_middleware.add(Amigo::DurableJob::ServerMiddleware)
|
|
61
|
+
# We use the dead set to move jobs that we need to retry manually
|
|
62
|
+
config.options[:dead_max_jobs] = 999_999_999
|
|
63
|
+
config.server_middleware.add(Amigo::Retry::ServerMiddleware)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Amigo::DurableJob.failure_notifier = Webhookdb::Async::JobLogger.method(:durable_job_failure_notifier)
|
|
67
|
+
|
|
68
|
+
Sidekiq.configure_client do |config|
|
|
69
|
+
config.redis = redis_params
|
|
70
|
+
config.client_middleware.add(Amigo::DurableJob::ClientMiddleware)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.open_web
|
|
76
|
+
u = URI(Webhookdb.api_url)
|
|
77
|
+
u.user = self.web_username
|
|
78
|
+
u.password = self.web_password
|
|
79
|
+
u.path = "/sidekiq"
|
|
80
|
+
`open #{u}`
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Set up async for the web/client side of things.
|
|
84
|
+
# This performs common Amigo config,
|
|
85
|
+
# and sets up the routing/auditing jobs.
|
|
86
|
+
#
|
|
87
|
+
# Note that we must also require all async jobs,
|
|
88
|
+
# since in some cases we may have sidekiq middleware that needs
|
|
89
|
+
# access to the actual job class, so it must be available.
|
|
90
|
+
def self.setup_web
|
|
91
|
+
self._setup_common
|
|
92
|
+
Amigo.install_amigo_jobs
|
|
93
|
+
self._require_jobs
|
|
94
|
+
return true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Set up the worker process.
|
|
98
|
+
# This peforms common Amigo config,
|
|
99
|
+
# sets up the routing/audit jobs (since jobs may publish to other jobs),
|
|
100
|
+
# requires the actual jobs,
|
|
101
|
+
# and starts the cron.
|
|
102
|
+
def self.setup_workers
|
|
103
|
+
self._setup_common
|
|
104
|
+
Amigo.install_amigo_jobs
|
|
105
|
+
self._require_jobs
|
|
106
|
+
Amigo.start_scheduler
|
|
107
|
+
return true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Set up for tests.
|
|
111
|
+
# This performs common config and requires the jobs.
|
|
112
|
+
# It does not install the routing/auditing jobs,
|
|
113
|
+
# since those should only be installed at specific times.
|
|
114
|
+
def self.setup_tests
|
|
115
|
+
return if Amigo.structured_logging # assume we are set up
|
|
116
|
+
self._setup_common
|
|
117
|
+
self._require_jobs
|
|
118
|
+
return true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self._require_jobs
|
|
122
|
+
Amigo::DurableJob.replace_database_settings(
|
|
123
|
+
loggers: [Webhookdb.logger],
|
|
124
|
+
**Webhookdb::Dbutil.configured_connection_options,
|
|
125
|
+
)
|
|
126
|
+
Gem.find_files(File.join("webhookdb/jobs/*.rb")).each do |path|
|
|
127
|
+
require path
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self._setup_common
|
|
132
|
+
raise "Async already setup, only call this once" if Amigo.structured_logging
|
|
133
|
+
Amigo.structured_logging = true
|
|
134
|
+
Amigo.log_callback = lambda { |j, lvl, msg, o|
|
|
135
|
+
lg = j ? Appydays::Loggable[j] : Webhookdb::Async::JobLogger.logger
|
|
136
|
+
lg.send(lvl, msg, o)
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
require "webhookdb/async/audit_logger"
|
|
142
|
+
Amigo.audit_logger_class = Webhookdb::Async::AuditLogger
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "aws-sdk-core"
|
|
4
|
+
require "aws-sdk-sts"
|
|
5
|
+
require "appydays/configurable"
|
|
6
|
+
|
|
7
|
+
require "webhookdb" unless defined?(Webhookdb)
|
|
8
|
+
|
|
9
|
+
module Webhookdb::AWS
|
|
10
|
+
extend Webhookdb::MethodUtilities
|
|
11
|
+
include Appydays::Configurable
|
|
12
|
+
include Appydays::Loggable
|
|
13
|
+
|
|
14
|
+
class ShimLogger
|
|
15
|
+
def initialize(logger, operation_prefix: "aws_", level: :debug, error_level: :warn)
|
|
16
|
+
@logger = logger
|
|
17
|
+
@param_formatter = ::Aws::Log::ParamFormatter.new({})
|
|
18
|
+
@param_filter = ::Aws::Log::ParamFilter.new({})
|
|
19
|
+
@operation_prefix = operation_prefix
|
|
20
|
+
@level = level
|
|
21
|
+
@error_level = error_level
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Logger method. Receives the unformatted response form the shim formatter,
|
|
25
|
+
# and logs a structured log.
|
|
26
|
+
# @param [Seahorse::Client::Response] response
|
|
27
|
+
def logshim(response)
|
|
28
|
+
level = @level
|
|
29
|
+
msg = @operation_prefix + response.context.operation_name.to_s
|
|
30
|
+
params = response.context.params
|
|
31
|
+
type = response.context.operation.input.shape.struct_class
|
|
32
|
+
ctx = {
|
|
33
|
+
aws_client: (response.context.client.class.name || "").delete_prefix("Aws::").delete_suffix("::Client"),
|
|
34
|
+
http_response_code: response.context.http_response.status_code,
|
|
35
|
+
elapsed: response.context[:logging_completed_at] - response.context[:logging_started_at],
|
|
36
|
+
request_params: @param_formatter.summarize(@param_filter.filter(params, type)),
|
|
37
|
+
}
|
|
38
|
+
ctx[:retries] = response.context.retries if response.context.retries.positive?
|
|
39
|
+
if response.error
|
|
40
|
+
level = @error_level
|
|
41
|
+
ctx[:error_class] = response.error.class.name
|
|
42
|
+
ctx[:error_message] = response.error.message
|
|
43
|
+
end
|
|
44
|
+
@logger.send(level, msg, ctx)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ShimFormatter
|
|
49
|
+
# Normally this must return a string, but we need to have it return a raw response
|
|
50
|
+
# for use in the shim logger.
|
|
51
|
+
# @param [Seahorse::Client::Response] response
|
|
52
|
+
# @return [String]
|
|
53
|
+
def format(response)
|
|
54
|
+
# noinspection RubyMismatchedReturnType
|
|
55
|
+
return response
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
singleton_attr_reader :sts_client
|
|
60
|
+
|
|
61
|
+
configurable(:aws) do
|
|
62
|
+
# WebhookDB's AWS Account ID.
|
|
63
|
+
# Used for things like cross-account role assumption.
|
|
64
|
+
setting :external_account_id, "054088425385"
|
|
65
|
+
|
|
66
|
+
# Some stuff doesn't work right with explicit config, so force-set the ENV
|
|
67
|
+
setting :access_key_id,
|
|
68
|
+
"default-access",
|
|
69
|
+
key: "AWS_ACCESS_KEY_ID",
|
|
70
|
+
side_effect: ->(v) { ENV["AWS_ACCESS_KEY_ID"] = v }
|
|
71
|
+
setting :secret_access_key,
|
|
72
|
+
"default-secret",
|
|
73
|
+
key: "AWS_SECRET_ACCESS_KEY",
|
|
74
|
+
side_effect: ->(v) { ENV["AWS_SECRET_ACCESS_KEY"] = v }
|
|
75
|
+
setting :region,
|
|
76
|
+
"us-west-2",
|
|
77
|
+
key: "AWS_REGION",
|
|
78
|
+
side_effect: ->(v) { ENV["AWS_REGION"] = v }
|
|
79
|
+
setting :http_timeout, 30
|
|
80
|
+
|
|
81
|
+
after_configured do
|
|
82
|
+
::Aws.config.update(
|
|
83
|
+
logger: ShimLogger.new(self.logger),
|
|
84
|
+
log_formatter: ShimFormatter.new,
|
|
85
|
+
log_level: :logshim,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@sts_client = Aws::STS::Client.new
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
REGIONS_TO_LOCATIONS = Aws.partitions.each_with_object({}) do |partition, h|
|
|
93
|
+
partition.regions.each do |region|
|
|
94
|
+
h[region.name] = region.description
|
|
95
|
+
end
|
|
96
|
+
end.freeze
|
|
97
|
+
LOCATIONS_TO_REGIONS = REGIONS_TO_LOCATIONS.to_a.to_h(&:reverse!).freeze
|
|
98
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Represents the boundaries around a single execution of backfilling an integration.
|
|
4
|
+
# Each instance points to a single run of a single integration.
|
|
5
|
+
# There may be child jobs pointing to dependent integrations,
|
|
6
|
+
# or a parent job for a dependency backfill.
|
|
7
|
+
#
|
|
8
|
+
# When creating jobs, you can create a single job (a 'shallow' backfill)
|
|
9
|
+
# with +create+, or use +create_recursive+ to create jobs for all dependencies
|
|
10
|
+
# (a 'job group').
|
|
11
|
+
#
|
|
12
|
+
# Each job tracks when the backfill starts and ends.
|
|
13
|
+
# Iterating the full job graph can determine if a group is fully finished,
|
|
14
|
+
# or still in-progress.
|
|
15
|
+
#
|
|
16
|
+
class Webhookdb::BackfillJob < Webhookdb::Postgres::Model(:backfill_jobs)
|
|
17
|
+
plugin :timestamps
|
|
18
|
+
|
|
19
|
+
many_to_one :service_integration, class: "Webhookdb::ServiceIntegration"
|
|
20
|
+
many_to_one :parent_job, class: "Webhookdb::BackfillJob"
|
|
21
|
+
one_to_many :child_jobs, class: "Webhookdb::BackfillJob", key: :parent_job_id
|
|
22
|
+
many_to_one :created_by, class: "Webhookdb::Customer"
|
|
23
|
+
|
|
24
|
+
attr_accessor :_fixture_cascade
|
|
25
|
+
|
|
26
|
+
# @return [Webhookdb::BackfillJob]
|
|
27
|
+
def self.create_recursive(service_integration:, incremental:, created_by: nil, parent_job: nil, criteria: nil)
|
|
28
|
+
self.db.transaction do
|
|
29
|
+
root = self.create(service_integration:, parent_job:, incremental:, created_by:, criteria: criteria || {})
|
|
30
|
+
root.setup_recursive
|
|
31
|
+
root
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# You should use ::create_recursive instead.
|
|
36
|
+
# This is mostly here for use in tests/fixtures.
|
|
37
|
+
def setup_recursive
|
|
38
|
+
raise Webhookdb::InvalidPrecondition, "already has children" if self.child_jobs.present?
|
|
39
|
+
self.service_integration.dependents.map do |dep|
|
|
40
|
+
self.class.create_recursive(service_integration: dep, parent_job: self, incremental:, criteria:)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def incremental? = self.incremental
|
|
45
|
+
|
|
46
|
+
def started? = !!self.started_at
|
|
47
|
+
def finished? = !!self.finished_at
|
|
48
|
+
|
|
49
|
+
def fully_finished_at
|
|
50
|
+
parent_finished = self.finished_at
|
|
51
|
+
return nil if parent_finished.nil?
|
|
52
|
+
children_finished = self.child_jobs.map(&:fully_finished_at)
|
|
53
|
+
return nil if children_finished.any?(&:nil?)
|
|
54
|
+
children_finished << parent_finished
|
|
55
|
+
return children_finished.max
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fully_finished? = !!self.fully_finished_at
|
|
59
|
+
|
|
60
|
+
def status
|
|
61
|
+
return "enqueued" unless self.started?
|
|
62
|
+
return "finished" if self.fully_finished?
|
|
63
|
+
return "inprogress"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def enqueue
|
|
67
|
+
self.publish_deferred("run", self.id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def enqueue_children
|
|
71
|
+
self.child_jobs.each(&:enqueue)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# :section: Sequel Hooks
|
|
76
|
+
#
|
|
77
|
+
|
|
78
|
+
def before_create
|
|
79
|
+
self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("bfj")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Table: backfill_jobs
|
|
84
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
85
|
+
# Columns:
|
|
86
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
87
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
88
|
+
# updated_at | timestamp with time zone |
|
|
89
|
+
# started_at | timestamp with time zone |
|
|
90
|
+
# finished_at | timestamp with time zone |
|
|
91
|
+
# opaque_id | text | NOT NULL
|
|
92
|
+
# service_integration_id | integer | NOT NULL
|
|
93
|
+
# parent_job_id | integer |
|
|
94
|
+
# created_by_id | integer |
|
|
95
|
+
# incremental | boolean | NOT NULL
|
|
96
|
+
# Indexes:
|
|
97
|
+
# backfill_jobs_pkey | PRIMARY KEY btree (id)
|
|
98
|
+
# backfill_jobs_opaque_id_key | UNIQUE btree (opaque_id)
|
|
99
|
+
# backfill_jobs_parent_job_id_index | btree (parent_job_id)
|
|
100
|
+
# backfill_jobs_service_integration_id_index | btree (service_integration_id)
|
|
101
|
+
# Foreign key constraints:
|
|
102
|
+
# backfill_jobs_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
103
|
+
# backfill_jobs_parent_job_id_fkey | (parent_job_id) REFERENCES backfill_jobs(id)
|
|
104
|
+
# backfill_jobs_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id) ON DELETE CASCADE
|
|
105
|
+
# Referenced By:
|
|
106
|
+
# backfill_jobs | backfill_jobs_parent_job_id_fkey | (parent_job_id) REFERENCES backfill_jobs(id)
|
|
107
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Implementation of a generic backfill pattern.
|
|
4
|
+
class Webhookdb::Backfiller
|
|
5
|
+
# Called for each item.
|
|
6
|
+
def handle_item(item)
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def fetch_backfill_page(pagination_token, last_backfilled:)
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Use nil last_backfilled for a full sync, pass it for an incremental.
|
|
15
|
+
# Should be service integration last_backfilled_at, the timestamp of
|
|
16
|
+
# the latest resource, etc.
|
|
17
|
+
def backfill(last_backfilled)
|
|
18
|
+
pagination_token = nil
|
|
19
|
+
loop do
|
|
20
|
+
page, next_pagination_token = self._fetch_backfill_page_with_retry(
|
|
21
|
+
pagination_token, last_backfilled:,
|
|
22
|
+
)
|
|
23
|
+
if page.nil?
|
|
24
|
+
msg = "Fetching a page should return an empty array, not nil. The service response probably is missing a key?"
|
|
25
|
+
raise TypeError, msg
|
|
26
|
+
end
|
|
27
|
+
pagination_token = next_pagination_token
|
|
28
|
+
page.each do |item|
|
|
29
|
+
self.handle_item(item)
|
|
30
|
+
end
|
|
31
|
+
Amigo::DurableJob.heartbeat
|
|
32
|
+
break if pagination_token.blank?
|
|
33
|
+
if Webhookdb.regression_mode?
|
|
34
|
+
Webhookdb.logger.warn("regression_mode_backfill_termination", backfiller: self.to_s, pagination_token:)
|
|
35
|
+
break
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
self.flush_pending_inserts if self.respond_to?(:flush_pending_inserts)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def max_backfill_retry_attempts
|
|
42
|
+
return 3
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def wait_for_retry_attempt(attempt:)
|
|
46
|
+
Webhookdb::Backfiller.do_retry_wait(attempt)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Make this easy to mock
|
|
50
|
+
def self.do_retry_wait(seconds)
|
|
51
|
+
Kernel.sleep(seconds)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def _fetch_backfill_page_with_retry(pagination_token, last_backfilled: nil, attempt: 1)
|
|
55
|
+
return self.fetch_backfill_page(pagination_token, last_backfilled:)
|
|
56
|
+
rescue Webhookdb::Http::BaseError => e
|
|
57
|
+
raise e if attempt >= self.max_backfill_retry_attempts
|
|
58
|
+
# Assume we'll never succeed on a 401, so don't bother retrying.
|
|
59
|
+
raise e if e.is_a?(Webhookdb::Http::Error) && e.status == 401
|
|
60
|
+
self.wait_for_retry_attempt(attempt:)
|
|
61
|
+
return self._fetch_backfill_page_with_retry(pagination_token, last_backfilled:, attempt: attempt + 1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module Bulk
|
|
65
|
+
def upsert_page_size = raise NotImplementedError("how many items should be upserted at a time")
|
|
66
|
+
def prepare_body(_body) = raise NotImplementedError("add/remove keys from body before upsert")
|
|
67
|
+
def upserting_replicator = raise NotImplementedError("the replicator being upserted")
|
|
68
|
+
def remote_key_column_name = @remote_key_column_name ||= self.upserting_replicator._remote_key_column.name
|
|
69
|
+
|
|
70
|
+
def pending_inserts = @pending_inserts ||= {}
|
|
71
|
+
# Should `_update_where_expr` be used or not?
|
|
72
|
+
# Default false, since most bulk upserting is backfill,
|
|
73
|
+
# which should only involve upserting new rows anyway.
|
|
74
|
+
def conditional_upsert? = false
|
|
75
|
+
|
|
76
|
+
def dry_run? = false
|
|
77
|
+
|
|
78
|
+
# Add the item to pending upserts, and run the page upsert if needed.
|
|
79
|
+
# Return the key, and the item being upserted.
|
|
80
|
+
# @return [Array(String, Hash),Array(nil)]
|
|
81
|
+
def handle_item(body)
|
|
82
|
+
self.prepare_body(body)
|
|
83
|
+
inserting = self.upserting_replicator.upsert_webhook_body(body, upsert: false)
|
|
84
|
+
return nil, nil if inserting.nil?
|
|
85
|
+
k = inserting.fetch(self.remote_key_column_name)
|
|
86
|
+
self.pending_inserts[k] = inserting
|
|
87
|
+
self.flush_pending_inserts if self.pending_inserts.size >= self.upsert_page_size
|
|
88
|
+
return k, inserting
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def flush_pending_inserts
|
|
92
|
+
return if self.dry_run?
|
|
93
|
+
return if self.pending_inserts.empty?
|
|
94
|
+
rows_to_insert = self.pending_inserts.values
|
|
95
|
+
update_where = self.conditional_upsert? ? self.upserting_replicator._update_where_expr : nil
|
|
96
|
+
self.upserting_replicator.admin_dataset(timeout: :fast) do |ds|
|
|
97
|
+
insert_ds = ds.insert_conflict(
|
|
98
|
+
target: self.upserting_replicator._remote_key_column.name,
|
|
99
|
+
update: self.upserting_replicator._upsert_update_expr(rows_to_insert.first),
|
|
100
|
+
update_where:,
|
|
101
|
+
)
|
|
102
|
+
insert_ds.multi_insert(rows_to_insert)
|
|
103
|
+
end
|
|
104
|
+
self.pending_inserts.clear
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
require "oj"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Cloudflare
|
|
8
|
+
include Appydays::Configurable
|
|
9
|
+
include Appydays::Loggable
|
|
10
|
+
|
|
11
|
+
configurable(:cloudflare) do
|
|
12
|
+
setting :api_token, "set-me-to-token"
|
|
13
|
+
setting :host, "https://api.cloudflare.com"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.headers
|
|
17
|
+
return {
|
|
18
|
+
"Authorization" => "Bearer #{self.api_token}",
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record
|
|
23
|
+
def self.create_zone_dns_record(name:, content:, zone_id:, type: "CNAME", ttl: 1)
|
|
24
|
+
body = {
|
|
25
|
+
type:,
|
|
26
|
+
name:,
|
|
27
|
+
content:,
|
|
28
|
+
ttl:,
|
|
29
|
+
}
|
|
30
|
+
response = Webhookdb::Http.post(
|
|
31
|
+
self.host + "/client/v4/zones/#{zone_id}/dns_records",
|
|
32
|
+
body,
|
|
33
|
+
headers: self.headers,
|
|
34
|
+
logger: self.logger,
|
|
35
|
+
timeout: nil,
|
|
36
|
+
)
|
|
37
|
+
return Oj.load(response.body)
|
|
38
|
+
end
|
|
39
|
+
end
|