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,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/email_octopus"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::EmailOctopusContactV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
|
|
8
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "email_octopus_contact_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::EmailOctopusContactV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Email Octopus Contact",
|
|
15
|
+
dependency_descriptor: Webhookdb::Replicator::EmailOctopusListV1.descriptor,
|
|
16
|
+
supports_backfill: true,
|
|
17
|
+
api_docs_url: "https://emailoctopus.com/api-documentation",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
CONV_REMOTE_KEY = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
22
|
+
ruby: ->(_, resource:, **_) { "#{resource.fetch('id')}-#{resource.fetch('list_id')}" },
|
|
23
|
+
# Because this is a non-nullable key, we never need this in SQL
|
|
24
|
+
sql: ->(_) { Sequel.lit("'do not use'") },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def _remote_key_column
|
|
28
|
+
return Webhookdb::Replicator::Column.new(
|
|
29
|
+
:compound_identity,
|
|
30
|
+
TEXT,
|
|
31
|
+
data_key: "<compound key, see converter>",
|
|
32
|
+
index: true,
|
|
33
|
+
optional: true,
|
|
34
|
+
converter: CONV_REMOTE_KEY,
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _denormalized_columns
|
|
39
|
+
return [
|
|
40
|
+
Webhookdb::Replicator::Column.new(:email_octopus_id, TEXT, data_key: "id"),
|
|
41
|
+
Webhookdb::Replicator::Column.new(:email_octopus_list_id, TEXT, data_key: "list_id"),
|
|
42
|
+
Webhookdb::Replicator::Column.new(:email_address, TEXT),
|
|
43
|
+
Webhookdb::Replicator::Column.new(:status, TEXT),
|
|
44
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true, converter: :time, skip_nil: true),
|
|
45
|
+
Webhookdb::Replicator::Column.new(:deleted_at, TIMESTAMP, converter: :time, optional: true),
|
|
46
|
+
Webhookdb::Replicator::Column.new(:row_updated_at, TIMESTAMP, defaulter: :now, optional: true),
|
|
47
|
+
]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _upsert_webhook(request, upsert: true)
|
|
51
|
+
return super unless request.body.is_a?(Array)
|
|
52
|
+
|
|
53
|
+
# If the body is an array, it means we are upserting data from webhooks, which has been passed to this function by
|
|
54
|
+
# the event replicator, and we have to transform the data in order to be able to upsert.
|
|
55
|
+
new_bodies = request.body.
|
|
56
|
+
# Events older than 30 days may not have occurred_at on free plans
|
|
57
|
+
select { |wh| wh.key?("occurred_at") }.
|
|
58
|
+
map do |wh|
|
|
59
|
+
event_type = wh.fetch("type")
|
|
60
|
+
{
|
|
61
|
+
"id" => wh.fetch("contact_id"),
|
|
62
|
+
"list_id" => wh.fetch("list_id"),
|
|
63
|
+
"email_address" => wh.fetch("contact_email_address"),
|
|
64
|
+
"status" => wh.fetch("contact_status"),
|
|
65
|
+
"row_updated_at" => wh.fetch("occurred_at"),
|
|
66
|
+
"created_at" => event_type == "contact.created" ? wh.fetch("occurred_at") : nil,
|
|
67
|
+
"deleted_at" => event_type == "contact.deleted" ? wh.fetch("occurred_at") : nil,
|
|
68
|
+
# These fields do not get denormalized but we still want the info
|
|
69
|
+
# to be present in the "data" field of the row.
|
|
70
|
+
"fields" => wh["contact_fields"],
|
|
71
|
+
"tags" => wh["contact_tags"],
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
new_bodies.each do |b|
|
|
75
|
+
new_request = request.dup
|
|
76
|
+
new_request.body = b
|
|
77
|
+
super(new_request, upsert:)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _resource_and_event(request)
|
|
82
|
+
return request.body, nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _update_where_expr
|
|
86
|
+
return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _timestamp_column_name
|
|
90
|
+
return :row_updated_at
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def _webhook_response(_request)
|
|
94
|
+
return Webhookdb::WebhookResponse.ok
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def calculate_backfill_state_machine
|
|
98
|
+
if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
99
|
+
return step
|
|
100
|
+
end
|
|
101
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
102
|
+
# We're using the API Key from the dependency, we don't need to ask for it here
|
|
103
|
+
step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
|
|
104
|
+
#{self._query_help_output}
|
|
105
|
+
)
|
|
106
|
+
return step.completed
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def on_dependency_webhook_upsert(_replicator, _payload, *)
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def _backfillers
|
|
114
|
+
list_sint = self.service_integration.depends_on
|
|
115
|
+
api_key = list_sint.replicator.backfill_key!
|
|
116
|
+
backfillers = list_sint.replicator.admin_dataset(timeout: :fast) do |list_ds|
|
|
117
|
+
list_ds.select(:email_octopus_id).map do |list|
|
|
118
|
+
ContactBackfiller.new(
|
|
119
|
+
contact_svc: self,
|
|
120
|
+
list_id: list[:email_octopus_id],
|
|
121
|
+
api_key:,
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
return backfillers
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class ContactBackfiller < Webhookdb::Backfiller
|
|
129
|
+
include Webhookdb::Backfiller::Bulk
|
|
130
|
+
|
|
131
|
+
def initialize(contact_svc:, list_id:, api_key:)
|
|
132
|
+
@contact_svc = contact_svc
|
|
133
|
+
@list_id = list_id
|
|
134
|
+
@api_key = api_key
|
|
135
|
+
super()
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def upserting_replicator = @contact_svc
|
|
139
|
+
def upsert_page_size = 500
|
|
140
|
+
|
|
141
|
+
def prepare_body(body)
|
|
142
|
+
body["list_id"] = @list_id
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def fetch_backfill_page(pagination_token, **_kwargs)
|
|
146
|
+
limit = Webhookdb::EmailOctopus.page_size
|
|
147
|
+
base_url = "https://emailoctopus.com"
|
|
148
|
+
endpoint_path = pagination_token || "/api/1.6/lists/#{@list_id}/contacts?api_key=#{@api_key}&limit=#{limit}"
|
|
149
|
+
response = Webhookdb::Http.get(
|
|
150
|
+
base_url + endpoint_path,
|
|
151
|
+
logger: @contact_svc.logger,
|
|
152
|
+
timeout: Webhookdb::EmailOctopus.http_timeout,
|
|
153
|
+
)
|
|
154
|
+
data = response.parsed_response
|
|
155
|
+
next_page_link = data.dig("paging", "next")
|
|
156
|
+
return data["data"], next_page_link
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/email_octopus"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::EmailOctopusEventV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
|
|
8
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "email_octopus_event_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::EmailOctopusEventV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Email Octopus Event",
|
|
15
|
+
dependency_descriptor: Webhookdb::Replicator::EmailOctopusListV1.descriptor,
|
|
16
|
+
supports_webhooks: true,
|
|
17
|
+
supports_backfill: true,
|
|
18
|
+
api_docs_url: "https://emailoctopus.com/api-documentation",
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
BUILD_EVENT_MD5 = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
22
|
+
ruby: lambda do |resource:, **|
|
|
23
|
+
# MD5 includes occurred_at, event_type, email_octopus_contact_id, and email_octopus_campaign_id.
|
|
24
|
+
md5 = Digest::MD5.new
|
|
25
|
+
md5.update(resource.fetch("occurred_at"))
|
|
26
|
+
md5.update(resource.fetch("event_type"))
|
|
27
|
+
md5.update(resource.dig("contact", "id"))
|
|
28
|
+
md5.update(resource.fetch("campaign_id", "missing"))
|
|
29
|
+
md5.hexdigest
|
|
30
|
+
end,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def _remote_key_column
|
|
34
|
+
return Webhookdb::Replicator::Column.new(:unique_id, UUID, optional: true, defaulter: BUILD_EVENT_MD5)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def _denormalized_columns
|
|
38
|
+
return [
|
|
39
|
+
Webhookdb::Replicator::Column.new(:email_octopus_contact_id, TEXT, data_key: ["contact", "id"]),
|
|
40
|
+
Webhookdb::Replicator::Column.new(:contact_email_address, TEXT, data_key: ["contact", "email_address"]),
|
|
41
|
+
Webhookdb::Replicator::Column.new(:email_octopus_campaign_id, TEXT, data_key: ["campaign_id"], optional: true),
|
|
42
|
+
Webhookdb::Replicator::Column.new(:event_type, TEXT),
|
|
43
|
+
Webhookdb::Replicator::Column.new(:occurred_at, TIMESTAMP, index: true, converter: :time),
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _timestamp_column_name
|
|
48
|
+
return :occurred_at
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def _upsert_webhook(request, upsert: true)
|
|
52
|
+
return super unless request.body.is_a?(Array)
|
|
53
|
+
|
|
54
|
+
# If the body is an array, it means we are upserting data from webhooks and have to transform the data
|
|
55
|
+
# in order to be able to upsert.
|
|
56
|
+
new_bodies = request.body.
|
|
57
|
+
# Events older than 30 days may not have occurred_at on free plans
|
|
58
|
+
select { |wh| wh.key?("occurred_at") }.
|
|
59
|
+
map do |wh|
|
|
60
|
+
new_body = {
|
|
61
|
+
"contact" => {
|
|
62
|
+
"id" => wh.fetch("contact_id"),
|
|
63
|
+
"email_address" => wh.fetch("contact_email_address"),
|
|
64
|
+
},
|
|
65
|
+
"occurred_at" => wh.fetch("occurred_at"),
|
|
66
|
+
"event_type" => wh.fetch("type"),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# "campaign_id" isn't always populated in the webhoooks, it is only there on event types that are tied
|
|
70
|
+
# to a specific "campaign", (that's Email Octopus's word for an email message), like "bounced" or "opened"
|
|
71
|
+
if (campaign_id = wh["campaign_id"])
|
|
72
|
+
new_body["campaign_id"] = campaign_id
|
|
73
|
+
end
|
|
74
|
+
new_body
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
new_bodies.each do |b|
|
|
78
|
+
new_request = request.dup
|
|
79
|
+
new_request.body = b
|
|
80
|
+
super(new_request, upsert:)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
list_svc = self.service_integration.depends_on.replicator
|
|
84
|
+
contact_sint = list_svc.find_dependent("email_octopus_contact_v1")
|
|
85
|
+
return unless contact_sint
|
|
86
|
+
contact_svc = contact_sint.replicator
|
|
87
|
+
# For events that pertain to a contact being created or updated in some way, we also upsert them using
|
|
88
|
+
# the contact integration so that the new information can be recorded in the contact table
|
|
89
|
+
contact_event_types = ["contact.created", "contact.updated", "contact.deleted"]
|
|
90
|
+
contact_events = request.body.filter { |nb| contact_event_types.include?(nb.fetch("type")) }
|
|
91
|
+
return if contact_events.empty?
|
|
92
|
+
contact_request = request.dup
|
|
93
|
+
contact_request.body = contact_events
|
|
94
|
+
contact_svc._upsert_webhook(contact_request, upsert:)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def _resource_and_event(request)
|
|
98
|
+
return nil, nil if request.body.is_a?(Hash) && !request.body.key?("occurred_at")
|
|
99
|
+
return request.body, nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def _update_where_expr
|
|
103
|
+
# 'occurred_at' is the timestamp, but it's used in the unique id,
|
|
104
|
+
# so there's no way to use it as part of the conditional upsert.
|
|
105
|
+
# So events are effectively immutable, and we know we shouldn't bother overwriting what's already in the DB.
|
|
106
|
+
return Sequel[false]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def _webhook_response(request)
|
|
110
|
+
signature_header = request.env["HTTP_EMAILOCTOPUS_SIGNATURE"]
|
|
111
|
+
return Webhookdb::WebhookResponse.error("missing signature") if signature_header.nil?
|
|
112
|
+
|
|
113
|
+
request.body.rewind
|
|
114
|
+
data = request.body.read
|
|
115
|
+
verified = Webhookdb::EmailOctopus.verify_webhook(data, signature_header, self.service_integration.webhook_secret)
|
|
116
|
+
return Webhookdb::WebhookResponse.ok if verified
|
|
117
|
+
return Webhookdb::WebhookResponse.error("invalid signature")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def calculate_webhook_state_machine
|
|
121
|
+
if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
122
|
+
return step
|
|
123
|
+
end
|
|
124
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
125
|
+
unless self.service_integration.webhook_secret.present?
|
|
126
|
+
step.output = %(You are about to start replicating #{self.resource_name_plural} into WebhookDB.
|
|
127
|
+
We've made an endpoint available for #{self.resource_name_singular} webhooks:
|
|
128
|
+
|
|
129
|
+
#{self._webhook_endpoint}
|
|
130
|
+
|
|
131
|
+
- From your Email Octopus dashboard, go to Account Settings -> Integrations & API.
|
|
132
|
+
- Then click the 'Manage' button next to 'Webhooks'.
|
|
133
|
+
- Then under the "Endpoints" header, click "Add endpoint"
|
|
134
|
+
- In the "URL" field you can enter the URL above.
|
|
135
|
+
- Then check boxes for all events, because we want this webhook to listen for everything.
|
|
136
|
+
This includes:
|
|
137
|
+
- "Contact events to send" -> "Created", "Updated", and "Deleted"
|
|
138
|
+
- "Email events to send" -> Deleted" "Clicked", "Opened", "Bounced",
|
|
139
|
+
"Complained", and "Unsubscribed"
|
|
140
|
+
- You can keep the checkboxes under "Exclude contact events that occur" unchecked.
|
|
141
|
+
|
|
142
|
+
- Save the endpoint.
|
|
143
|
+
|
|
144
|
+
You'll be dropped back on the Webhooks page.
|
|
145
|
+
Click 'View Secret' next to the endpoint you added, and Copy it.
|
|
146
|
+
We'll use it for webhook verification.)
|
|
147
|
+
return step.secret_prompt("webhook secret").webhook_secret(self.service_integration)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
step.output = %(Great! WebhookDB is now listening for #{self.resource_name_singular} webhooks.
|
|
151
|
+
#{self._query_help_output}
|
|
152
|
+
In order to backfill existing #{self.resource_name_plural}, run this from a shell:
|
|
153
|
+
|
|
154
|
+
#{self._backfill_command})
|
|
155
|
+
return step.completed
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def calculate_backfill_state_machine
|
|
159
|
+
if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
160
|
+
return step
|
|
161
|
+
end
|
|
162
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
163
|
+
# We're using the API Key from the dependency, we don't need to ask for it here
|
|
164
|
+
step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
|
|
165
|
+
#{self._query_help_output})
|
|
166
|
+
return step.completed
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def on_dependency_webhook_upsert(_replicator, _payload, *)
|
|
170
|
+
return
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
CAMPAIGN_EVENT_TYPES = [
|
|
174
|
+
"bounced",
|
|
175
|
+
"clicked",
|
|
176
|
+
"complained",
|
|
177
|
+
"opened",
|
|
178
|
+
"unsubscribed",
|
|
179
|
+
].freeze
|
|
180
|
+
|
|
181
|
+
def _backfillers
|
|
182
|
+
list_sint = self.service_integration.depends_on
|
|
183
|
+
api_key = list_sint.replicator.backfill_key!
|
|
184
|
+
campaign_sint = list_sint.replicator.find_dependent!("email_octopus_campaign_v1")
|
|
185
|
+
campaign_svc = campaign_sint.replicator
|
|
186
|
+
backfillers = campaign_svc.admin_dataset(timeout: :fast) do |campaign_ds|
|
|
187
|
+
campaign_ds.select(:email_octopus_id).flat_map do |campaign|
|
|
188
|
+
CAMPAIGN_EVENT_TYPES.map do |event_type|
|
|
189
|
+
EventBackfiller.new(
|
|
190
|
+
event_svc: self,
|
|
191
|
+
campaign_id: campaign[:email_octopus_id],
|
|
192
|
+
api_key:,
|
|
193
|
+
event_type:,
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
return backfillers
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Because "Event" is an abstraction we've created and does not actually exist in the Email Octopus API,
|
|
202
|
+
# it might seem strange that we are able to "backfill" the resources at all. However, the API has a number of
|
|
203
|
+
# endpoints that they call "campaign reports," which return lists of contacts that have engaged with a campaign
|
|
204
|
+
# in the specified way (e.g. bounced, opened, etc.) In this way, we can retrieve timestamped records of "events" that
|
|
205
|
+
# have already occurred. The only available campaign report endpoint that we don't hit is "sent", which notes the time
|
|
206
|
+
# at which each campaign was sent to each contact. This information doesn't come in through webhooks at all and the
|
|
207
|
+
# timestamps closely match the "sent_at" field in the campaign row, so we have opted not to track it through backfill.
|
|
208
|
+
class EventBackfiller < Webhookdb::Backfiller
|
|
209
|
+
include Webhookdb::Backfiller::Bulk
|
|
210
|
+
|
|
211
|
+
def initialize(event_svc:, campaign_id:, api_key:, event_type:)
|
|
212
|
+
@event_svc = event_svc
|
|
213
|
+
@campaign_id = campaign_id
|
|
214
|
+
@api_key = api_key
|
|
215
|
+
@event_type = event_type
|
|
216
|
+
super()
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def upserting_replicator = @event_svc
|
|
220
|
+
def upsert_page_size = 500
|
|
221
|
+
|
|
222
|
+
def prepare_body(body)
|
|
223
|
+
body["campaign_id"] = @campaign_id
|
|
224
|
+
body["event_type"] = "contact.#{@event_type}"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def fetch_backfill_page(pagination_token, **_kwargs)
|
|
228
|
+
limit = Webhookdb::EmailOctopus.page_size
|
|
229
|
+
base_url = "https://emailoctopus.com"
|
|
230
|
+
# rubocop:disable Layout/LineLength
|
|
231
|
+
endpoint_path = pagination_token || "/api/1.6/campaigns/#{@campaign_id}/reports/#{@event_type}?api_key=#{@api_key}&limit=#{limit}"
|
|
232
|
+
# rubocop:enable Layout/LineLength
|
|
233
|
+
response = Webhookdb::Http.get(
|
|
234
|
+
base_url + endpoint_path,
|
|
235
|
+
logger: @event_svc.logger,
|
|
236
|
+
timeout: Webhookdb::EmailOctopus.http_timeout,
|
|
237
|
+
)
|
|
238
|
+
data = response.parsed_response
|
|
239
|
+
# if no data is returned from endpoint, the "paging" and "data" values are both empty arrays
|
|
240
|
+
next_page_link = data.fetch("paging").empty? ? nil : data.dig("paging", "next")
|
|
241
|
+
return data["data"], next_page_link
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/email_octopus"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::EmailOctopusListV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
|
|
8
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "email_octopus_list_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::EmailOctopusListV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Email Octopus List",
|
|
15
|
+
supports_backfill: true,
|
|
16
|
+
api_docs_url: "https://emailoctopus.com/api-documentation",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _remote_key_column
|
|
21
|
+
return Webhookdb::Replicator::Column.new(:email_octopus_id, TEXT, data_key: "id")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _denormalized_columns
|
|
25
|
+
return [
|
|
26
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true, converter: :time),
|
|
28
|
+
Webhookdb::Replicator::Column.new(:pending, INTEGER, data_key: ["counts", "pending"]),
|
|
29
|
+
Webhookdb::Replicator::Column.new(:subscribed, INTEGER, data_key: ["counts", "subscribed"]),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:unsubscribed, INTEGER, data_key: ["counts", "unsubscribed"]),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:row_updated_at, TIMESTAMP, defaulter: :now, optional: true),
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _resource_and_event(request)
|
|
36
|
+
return request.body, nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def _update_where_expr
|
|
40
|
+
return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def _timestamp_column_name
|
|
44
|
+
return :row_updated_at
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _webhook_response(_request)
|
|
48
|
+
return Webhookdb::WebhookResponse.ok
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calculate_backfill_state_machine
|
|
52
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
53
|
+
unless self.service_integration.backfill_key.present?
|
|
54
|
+
step.output = %(In order to replicate #{self.resource_name_plural} into WebhookDB, we need an API Key.
|
|
55
|
+
From your Email Octopus dashboard, go to Account Settings -> Integrations & API.
|
|
56
|
+
Then, click through to the API menu, under the "Developer tools" header and create a key.
|
|
57
|
+
|
|
58
|
+
Copy the key.
|
|
59
|
+
)
|
|
60
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
64
|
+
self.service_integration.replicator.clear_backfill_information
|
|
65
|
+
step.output = result.message
|
|
66
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
|
|
70
|
+
#{self._query_help_output}
|
|
71
|
+
)
|
|
72
|
+
return step.completed
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def _verify_backfill_err_msg
|
|
76
|
+
return "It looks like that API key is invalid. Please reenter your API Key:"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def _fetch_backfill_page(pagination_token, **)
|
|
80
|
+
api_key = self.backfill_key!
|
|
81
|
+
limit = Webhookdb::EmailOctopus.page_size
|
|
82
|
+
base_url = "https://emailoctopus.com"
|
|
83
|
+
endpoint_path = pagination_token || "/api/1.6/lists?api_key=#{api_key}&limit=#{limit}"
|
|
84
|
+
response = Webhookdb::Http.get(
|
|
85
|
+
base_url + endpoint_path,
|
|
86
|
+
logger: self.logger,
|
|
87
|
+
timeout: Webhookdb::EmailOctopus.http_timeout,
|
|
88
|
+
)
|
|
89
|
+
data = response.parsed_response
|
|
90
|
+
next_page_link = data.dig("paging", "next")
|
|
91
|
+
return data["data"], next_page_link
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def backfill_key!
|
|
95
|
+
bk = self.service_integration.backfill_key
|
|
96
|
+
|
|
97
|
+
return bk if bk.present?
|
|
98
|
+
raise Webhookdb::Replicator::CredentialsMissing,
|
|
99
|
+
"This integration requires that the #{self.descriptor.name} integration has a valid API Key"
|
|
100
|
+
end
|
|
101
|
+
end
|