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,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require "webhookdb/convertkit"
|
|
5
|
+
require "webhookdb/replicator/convertkit_v1_mixin"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Replicator::ConvertkitSubscriberV1 < Webhookdb::Replicator::Base
|
|
8
|
+
include Appydays::Loggable
|
|
9
|
+
include Webhookdb::Replicator::ConvertkitV1Mixin
|
|
10
|
+
|
|
11
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
12
|
+
def self.descriptor
|
|
13
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
14
|
+
name: "convertkit_subscriber_v1",
|
|
15
|
+
ctor: ->(sint) { Webhookdb::Replicator::ConvertkitSubscriberV1.new(sint) },
|
|
16
|
+
feature_roles: [],
|
|
17
|
+
resource_name_singular: "ConvertKit Subscriber",
|
|
18
|
+
supports_webhooks: true,
|
|
19
|
+
supports_backfill: true,
|
|
20
|
+
description: "Replicate ConvertKit subscribers into a database. " \
|
|
21
|
+
"This is one of the only ways you can keep track of historical subscriber information " \
|
|
22
|
+
"with ConvertKit.",
|
|
23
|
+
api_docs_url: "https://developers.convertkit.com/#list-subscribers",
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def process_state_change(field, value)
|
|
28
|
+
step = super
|
|
29
|
+
self._create_webhooks if field == "backfill_secret"
|
|
30
|
+
return step
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _create_webhooks
|
|
34
|
+
# ConvertKit has made several other webhooks available for the subscriber object, but they all have required
|
|
35
|
+
# parameters that are pks of other objects that webhookdb knows nothing about.
|
|
36
|
+
|
|
37
|
+
# first verify that the webhooks don't exist
|
|
38
|
+
url = "https://api.convertkit.com/v3/automations/hooks?api_secret=#{self.service_integration.backfill_secret}"
|
|
39
|
+
response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
|
|
40
|
+
# the data returned here is a list of the existing webhooks
|
|
41
|
+
data = response.parsed_response
|
|
42
|
+
|
|
43
|
+
# does the "subscriber.subscriber_activate" exist? if not, create it
|
|
44
|
+
# rubocop:disable Style/GuardClause
|
|
45
|
+
if data.present?
|
|
46
|
+
sub_activate_webhook = data.find do |obj|
|
|
47
|
+
obj.dig("rule", "event", "name") == "subscriber_activate"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
unless sub_activate_webhook.present?
|
|
51
|
+
Webhookdb::Http.post(
|
|
52
|
+
"https://api.convertkit.com/v3/automations/hooks",
|
|
53
|
+
{
|
|
54
|
+
"api_secret" => self.service_integration.backfill_secret,
|
|
55
|
+
"target_url" => self.webhook_endpoint,
|
|
56
|
+
"event" => {"name" => "subscriber.subscriber_activate"},
|
|
57
|
+
},
|
|
58
|
+
logger: self.logger,
|
|
59
|
+
timeout: Webhookdb::Convertkit.http_timeout,
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# does the "subscriber.subscriber_activate" exist? if not, create it
|
|
64
|
+
if data.present?
|
|
65
|
+
sub_unsubscribe_webhook = data.find do |obj|
|
|
66
|
+
obj.dig("rule", "event", "name") == "subscriber_activate"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
unless sub_unsubscribe_webhook.present?
|
|
70
|
+
Webhookdb::Http.post(
|
|
71
|
+
"https://api.convertkit.com/v3/automations/hooks",
|
|
72
|
+
{
|
|
73
|
+
"api_secret" => self.service_integration.backfill_secret,
|
|
74
|
+
"target_url" => self.webhook_endpoint,
|
|
75
|
+
"event" => {"name" => "subscriber.subscriber_unsubscribe"},
|
|
76
|
+
},
|
|
77
|
+
logger: self.logger,
|
|
78
|
+
timeout: Webhookdb::Convertkit.http_timeout,
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
# rubocop:enable Style/GuardClause
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def calculate_webhook_state_machine
|
|
85
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
86
|
+
step.output = %(
|
|
87
|
+
Great! We've created your ConvertKit Subscribers integration.
|
|
88
|
+
|
|
89
|
+
ConvertKit supports Subscriber webhooks.
|
|
90
|
+
You have two options for hooking them up:
|
|
91
|
+
|
|
92
|
+
A: Create the webhook yourself, so you don't need to provide us an API Secret.
|
|
93
|
+
Run this from a shell:
|
|
94
|
+
|
|
95
|
+
curl -X POST https://api.convertkit.com/v3/automations/hooks
|
|
96
|
+
-H 'Content-Type: application/json'\\
|
|
97
|
+
-d '{ "api_secret": "<your_secret_api_key>",\\
|
|
98
|
+
"target_url": "#{self._webhook_endpoint}",\\
|
|
99
|
+
"event": { "name": "subscriber.subscriber_activate" } }'
|
|
100
|
+
curl -X POST https://api.convertkit.com/v3/automations/hooks
|
|
101
|
+
-H 'Content-Type: application/json'\\
|
|
102
|
+
-d '{ "api_secret": "<your_secret_api_key>",\\
|
|
103
|
+
"target_url": "#{self._webhook_endpoint}",\\
|
|
104
|
+
"event": { "name": "subscriber.subscriber_unsubscribe" } }'
|
|
105
|
+
|
|
106
|
+
B: Use WebhookDB to backfill historical data with your API Secret, and when we do this,
|
|
107
|
+
we'll also set up webhooks for new data.
|
|
108
|
+
To start backfilling historical data, run this from a shell:
|
|
109
|
+
|
|
110
|
+
#{self._backfill_command}
|
|
111
|
+
|
|
112
|
+
Once you have data (you set up the webhooks, or ran the 'backfill' command),
|
|
113
|
+
your database will be populated.
|
|
114
|
+
#{self._query_help_output}
|
|
115
|
+
)
|
|
116
|
+
return step.completed
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def _denormalized_columns
|
|
120
|
+
return [
|
|
121
|
+
Webhookdb::Replicator::Column.new(
|
|
122
|
+
:canceled_at,
|
|
123
|
+
TIMESTAMP,
|
|
124
|
+
index: true,
|
|
125
|
+
optional: true,
|
|
126
|
+
converter: CONV_FIND_CANCELED_AT,
|
|
127
|
+
),
|
|
128
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "created_at", index: true),
|
|
129
|
+
Webhookdb::Replicator::Column.new(:email_address, TEXT, index: true),
|
|
130
|
+
Webhookdb::Replicator::Column.new(:first_name, TEXT),
|
|
131
|
+
Webhookdb::Replicator::Column.new(:last_name, TEXT, data_key: ["fields", "last_name"], optional: true),
|
|
132
|
+
Webhookdb::Replicator::Column.new(:state, TEXT),
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def _timestamp_column_name
|
|
137
|
+
return :created_at
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def _resource_and_event(request)
|
|
141
|
+
body = request.body
|
|
142
|
+
return body["subscriber"], body if body.key?("subscriber").present?
|
|
143
|
+
return body, nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def _update_where_expr
|
|
147
|
+
return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def _upsert_update_expr(inserting, **_kwargs)
|
|
151
|
+
update = super
|
|
152
|
+
state = inserting.fetch(:state)
|
|
153
|
+
# If the state is active, we want to use canceled_at:nil unconditionally.
|
|
154
|
+
return update if state == "active"
|
|
155
|
+
# If it's inactive, we only want to update canceled_at if it's not already set
|
|
156
|
+
# (coalesce the existing row's canceled_at with the 'time.now' we are passing in).
|
|
157
|
+
self._coalesce_excluded_on_update(update, [:canceled_at])
|
|
158
|
+
return update
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def _fetch_backfill_page(pagination_token, last_backfilled:)
|
|
162
|
+
pagination_token ||= ["subscribed", 1]
|
|
163
|
+
list_being_iterated, page = pagination_token
|
|
164
|
+
|
|
165
|
+
url = "https://api.convertkit.com/v3/subscribers?api_secret=#{self.service_integration.backfill_secret}&page=#{page}&sort_order=desc"
|
|
166
|
+
url += "&updated_from=#{last_backfilled.strftime('%FT%TZ')}" if last_backfilled.present?
|
|
167
|
+
url += "&sort_field=cancelled_at" if list_being_iterated == "cancelled"
|
|
168
|
+
|
|
169
|
+
response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
|
|
170
|
+
data = response.parsed_response
|
|
171
|
+
current_page = data["page"]
|
|
172
|
+
total_pages = data["total_pages"]
|
|
173
|
+
subs = data["subscribers"]
|
|
174
|
+
|
|
175
|
+
if last_backfilled.present?
|
|
176
|
+
earliest_data_created = subs.empty? ? Time.at(0) : subs[-1].fetch("created_at")
|
|
177
|
+
paged_to_already_seen_records = earliest_data_created < last_backfilled
|
|
178
|
+
|
|
179
|
+
if paged_to_already_seen_records && list_being_iterated == "subscribed"
|
|
180
|
+
# If we are done backfilling from the 'subscribed' list, we can now iterate cancelled
|
|
181
|
+
return subs, ["cancelled", 1]
|
|
182
|
+
end
|
|
183
|
+
if paged_to_already_seen_records && list_being_iterated == "cancelled"
|
|
184
|
+
# If we are done backfilling from the 'cancelled' list, we are done backfilling
|
|
185
|
+
return subs, nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if current_page < total_pages
|
|
190
|
+
# If we still have pages on this list, go to the next one
|
|
191
|
+
return subs, [list_being_iterated, current_page + 1]
|
|
192
|
+
end
|
|
193
|
+
if list_being_iterated == "subscribed"
|
|
194
|
+
# If we are done with the 'subscribed' list, we can now iterate cancelled
|
|
195
|
+
return subs, ["cancelled", 1]
|
|
196
|
+
end
|
|
197
|
+
# Otherwise, we're at the last page of our canceled subscribers list
|
|
198
|
+
return subs, nil
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require "webhookdb/convertkit"
|
|
5
|
+
require "webhookdb/replicator/convertkit_v1_mixin"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Replicator::ConvertkitTagV1 < Webhookdb::Replicator::Base
|
|
8
|
+
include Appydays::Loggable
|
|
9
|
+
include Webhookdb::Replicator::ConvertkitV1Mixin
|
|
10
|
+
|
|
11
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
12
|
+
def self.descriptor
|
|
13
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
14
|
+
name: "convertkit_tag_v1",
|
|
15
|
+
ctor: ->(sint) { Webhookdb::Replicator::ConvertkitTagV1.new(sint) },
|
|
16
|
+
feature_roles: [],
|
|
17
|
+
resource_name_singular: "ConvertKit Tag",
|
|
18
|
+
supports_backfill: true,
|
|
19
|
+
api_docs_url: "https://developers.convertkit.com/#list-tags",
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _denormalized_columns
|
|
24
|
+
return [
|
|
25
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "created_at", index: true),
|
|
26
|
+
Webhookdb::Replicator::Column.new(:name, TEXT, index: true),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:total_subscriptions, INTEGER, from_enrichment: true),
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _timestamp_column_name
|
|
32
|
+
return :created_at
|
|
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 upsert_has_deps?
|
|
44
|
+
return true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _store_enrichment_body?
|
|
48
|
+
return true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def _fetch_enrichment(resource, _event, _request)
|
|
52
|
+
tag_id = resource.fetch("id")
|
|
53
|
+
url = "https://api.convertkit.com/v3/tags/#{tag_id}/subscriptions?api_secret=#{self.service_integration.backfill_secret}"
|
|
54
|
+
response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
|
|
55
|
+
data = response.parsed_response
|
|
56
|
+
return data
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def _fetch_backfill_page(_pagination_token, **_kwargs)
|
|
60
|
+
# this endpoint does not have pagination support
|
|
61
|
+
url = "https://api.convertkit.com/v3/tags?api_secret=#{self.service_integration.backfill_secret}"
|
|
62
|
+
response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
|
|
63
|
+
data = response.parsed_response
|
|
64
|
+
return data["tags"], nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::Replicator::ConvertkitV1Mixin
|
|
4
|
+
include Webhookdb::DBAdapter::ColumnTypes
|
|
5
|
+
|
|
6
|
+
# ConvertKit gets a decent number of 5xx responses.
|
|
7
|
+
# Wait for about 20 minutes before the job dies.
|
|
8
|
+
def backfiller_server_error_retries = 10
|
|
9
|
+
def backfiller_server_error_backoff = 121.seconds
|
|
10
|
+
|
|
11
|
+
def _webhook_response(_request)
|
|
12
|
+
# Webhook Authentication isn't supported
|
|
13
|
+
return Webhookdb::WebhookResponse.ok
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def _remote_key_column
|
|
17
|
+
return Webhookdb::Replicator::Column.new(:convertkit_id, BIGINT, data_key: "id")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def calculate_backfill_state_machine
|
|
21
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
22
|
+
if self.service_integration.backfill_secret.blank?
|
|
23
|
+
step.output = %(Great! We've created your #{self.resource_name_plural} integration.
|
|
24
|
+
|
|
25
|
+
#{self.resource_name_singular} webhooks are not designed to mirror data, so to fill your database,
|
|
26
|
+
we need to use the API to make requests, which requires your API Secret.
|
|
27
|
+
#{Webhookdb::Convertkit::FIND_API_SECRET_HELP}
|
|
28
|
+
)
|
|
29
|
+
return step.secret_prompt("API Secret").backfill_secret(self.service_integration)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
33
|
+
self.service_integration.replicator.clear_backfill_information
|
|
34
|
+
step.output = result.message
|
|
35
|
+
return step.secret_prompt("API Secret").backfill_secret(self.service_integration)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
step.output = %(We'll start backfilling your #{self.resource_name_plural} now,
|
|
39
|
+
and they will show up in your database momentarily.
|
|
40
|
+
#{self._query_help_output}
|
|
41
|
+
)
|
|
42
|
+
return step.completed
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _verify_backfill_401_err_msg
|
|
46
|
+
return "It looks like that API Secret is invalid. Please reenter the API Secret:"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _verify_backfill_err_msg
|
|
50
|
+
return "An error occurred. Please reenter the API Secret:"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Converter for use with the denormalized columns
|
|
54
|
+
CONV_FIND_CANCELED_AT = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
55
|
+
ruby: lambda do |_, resource:, **_|
|
|
56
|
+
# Subscribers do not store a cancelation time (nor an updated at time),
|
|
57
|
+
# so we derive and store it based on their state.
|
|
58
|
+
# When they become inactive state, we set canceled_at,
|
|
59
|
+
# and clear it when they are not active.
|
|
60
|
+
# See the upsert_update_expr for Convertkit Subscriber for the details.
|
|
61
|
+
resource.fetch("state") == "active" ? nil : Time.now
|
|
62
|
+
end,
|
|
63
|
+
sql: nil,
|
|
64
|
+
)
|
|
65
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Write docs for docs.webhookdb.com Jekyll site.
|
|
4
|
+
class Webhookdb::Replicator::Docgen
|
|
5
|
+
def self.documentable_descriptors
|
|
6
|
+
return Webhookdb::Replicator.registry.values.reject do |repl|
|
|
7
|
+
repl.name.start_with?("webhookdb_", "fake_")
|
|
8
|
+
end.sort_by(&:name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @!attribute desc
|
|
12
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
13
|
+
attr_reader :desc
|
|
14
|
+
attr_reader :lines
|
|
15
|
+
|
|
16
|
+
# @param desc [Webhookdb::Replicator::Descriptor]
|
|
17
|
+
def initialize(desc)
|
|
18
|
+
@desc = desc
|
|
19
|
+
@lines = []
|
|
20
|
+
@documentable_descriptors = self.class.documentable_descriptors
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sint
|
|
24
|
+
@sint ||= Webhookdb::ServiceIntegration.new(
|
|
25
|
+
service_name: desc.name,
|
|
26
|
+
opaque_id: "svi_fixture",
|
|
27
|
+
table_name: desc.name + "_fixture",
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def repl
|
|
32
|
+
@repl ||= desc.ctor[sint]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def markdown
|
|
36
|
+
_frontmatter
|
|
37
|
+
_intro
|
|
38
|
+
_features
|
|
39
|
+
_schema
|
|
40
|
+
_tabledef
|
|
41
|
+
_prevnext
|
|
42
|
+
return lines.join("\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _frontmatter
|
|
46
|
+
lines << "---"
|
|
47
|
+
lines << "title: #{desc.resource_name_singular}"
|
|
48
|
+
lines << "layout: home"
|
|
49
|
+
idx = @documentable_descriptors.index(desc)
|
|
50
|
+
lines << "nav_order: #{(idx + 1) * 10}"
|
|
51
|
+
lines << "---"
|
|
52
|
+
lines << ""
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def _intro
|
|
56
|
+
lines << "# #{desc.resource_name_singular} (`#{desc.name}`)"
|
|
57
|
+
if desc.description.present?
|
|
58
|
+
lines << ""
|
|
59
|
+
lines << desc.description
|
|
60
|
+
end
|
|
61
|
+
if desc.api_docs_url.present?
|
|
62
|
+
lines << ""
|
|
63
|
+
lines << "Docs for this API: [#{desc.api_docs_url}](#{desc.api_docs_url})"
|
|
64
|
+
end
|
|
65
|
+
lines << ""
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _features
|
|
69
|
+
lines << "## Features"
|
|
70
|
+
lines << ""
|
|
71
|
+
lines << "<dl>"
|
|
72
|
+
if (dep = desc.dependency_descriptor)
|
|
73
|
+
lines << "<dt>Depends on</dt>"
|
|
74
|
+
lines << "<dd>To use this replicator, you will need #{refanchor(dep)}. " \
|
|
75
|
+
"You'll be prompted to create it if you haven't.</dd>"
|
|
76
|
+
lines << ""
|
|
77
|
+
end
|
|
78
|
+
deps = @documentable_descriptors.select { |d| d.dependency_descriptor == desc }
|
|
79
|
+
if deps.any?
|
|
80
|
+
lines << "<dt>Dependents</dt>"
|
|
81
|
+
lines << "<dd>This replicator is required for the creation of the following dependents:"
|
|
82
|
+
lines << "<ul>"
|
|
83
|
+
deps.each { |d| lines << "<li>#{refanchor(d)}</li>" }
|
|
84
|
+
lines << "</ul>"
|
|
85
|
+
lines << "</dd>"
|
|
86
|
+
lines << ""
|
|
87
|
+
end
|
|
88
|
+
lines << "<dt>Supports Webhooks</dt>"
|
|
89
|
+
lines << "<dd>#{boolmoji(desc.supports_webhooks?)}</dd>"
|
|
90
|
+
lines << "<dt>Supports Backfilling</dt>"
|
|
91
|
+
lines << "<dd>#{boolmoji(desc.supports_backfill?)}</dd>"
|
|
92
|
+
if desc.enterprise?
|
|
93
|
+
lines << "<dt>Enterprise Only</dt>"
|
|
94
|
+
lines << "<dd>Yes</dd>"
|
|
95
|
+
end
|
|
96
|
+
lines << ""
|
|
97
|
+
lines << "</dl>"
|
|
98
|
+
lines << ""
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def _schema
|
|
102
|
+
lines << "## Schema"
|
|
103
|
+
lines << ""
|
|
104
|
+
lines << "Tables replicated from #{desc.resource_name_plural} have this schema.
|
|
105
|
+
Note that the data types listed are for Postgres;
|
|
106
|
+
when [replicating to other databases]({% link _concepts/replication_databases.md %}),
|
|
107
|
+
other data types maybe used."
|
|
108
|
+
lines << ""
|
|
109
|
+
lines << "| Column | Type | Indexed |"
|
|
110
|
+
columns = [repl.primary_key_column, repl.remote_key_column]
|
|
111
|
+
columns.concat(repl.storable_columns)
|
|
112
|
+
columns << repl.data_column
|
|
113
|
+
columns.each do |c|
|
|
114
|
+
name = "`#{c.name}`"
|
|
115
|
+
(name += "*") if c.name == :data
|
|
116
|
+
lines << "| #{name} | `#{pgtype(c.type)}` | #{truecheck(c.index)} |"
|
|
117
|
+
end
|
|
118
|
+
lines << ""
|
|
119
|
+
lines << <<~S
|
|
120
|
+
<span class="fs-3">* The `data` column contains the raw payload from the webhook or API.
|
|
121
|
+
In many cases there is no canonical form, like if a webhook and API request return
|
|
122
|
+
two different versions of the same resource.
|
|
123
|
+
In that case we try to keep the most coherent and detailed resource."</span>
|
|
124
|
+
S
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def _tabledef
|
|
128
|
+
lines << "## Table definition"
|
|
129
|
+
lines << ""
|
|
130
|
+
lines << "This definition can also be generated through `webhookdb fixture #{desc.name}`."
|
|
131
|
+
lines << ""
|
|
132
|
+
lines << "```sql"
|
|
133
|
+
lines << repl.create_table_modification.to_s
|
|
134
|
+
lines << "```"
|
|
135
|
+
lines << ""
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def _prevnext
|
|
139
|
+
idx = @documentable_descriptors.index(desc)
|
|
140
|
+
raise Webhookdb::InvariantViolation if idx.nil?
|
|
141
|
+
prevtxt = nexttxt = ""
|
|
142
|
+
if (rprev = idx.zero? ? nil : @documentable_descriptors[idx - 1])
|
|
143
|
+
prevtxt = "prev='_integrations/#{rprev.name}.md' prevLabel='#{rprev.name}' "
|
|
144
|
+
end
|
|
145
|
+
if (rnext = idx == (@documentable_descriptors.size - 1) ? nil : @documentable_descriptors[idx + 1])
|
|
146
|
+
nexttxt = "next='_integrations/#{rnext.name}.md' nextLabel='#{rnext.name}'"
|
|
147
|
+
end
|
|
148
|
+
lines << "{% include prevnext.html #{prevtxt}#{nexttxt} %}"
|
|
149
|
+
lines << ""
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def refhref(d) = "{% link _integrations/#{d.name}.md %}"
|
|
153
|
+
def refanchor(d) = "<a href=\"#{refhref(d)}\">#{d.name}</a>"
|
|
154
|
+
def boolmoji(b) = b ? "✅" : "❌"
|
|
155
|
+
def truecheck(b) = b ? "✅" : ""
|
|
156
|
+
def pgtype(t) = Webhookdb::DBAdapter::PG::COLTYPE_MAP[t]
|
|
157
|
+
|
|
158
|
+
def self.replicator_list_md(descriptors)
|
|
159
|
+
lines = []
|
|
160
|
+
descriptors.each do |d|
|
|
161
|
+
line = "- [#{d.resource_name_singular}]({% link _integrations/#{d.name}.md %})"
|
|
162
|
+
line += " ([Enterprise]({% link docs/enterprise.md %}) only)" if d.enterprise
|
|
163
|
+
lines << line
|
|
164
|
+
end
|
|
165
|
+
return lines.join("\n")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/email_octopus"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::EmailOctopusCampaignV1 < 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_campaign_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::EmailOctopusCampaignV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Email Octopus Campaign",
|
|
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
|
+
def _remote_key_column
|
|
22
|
+
return Webhookdb::Replicator::Column.new(:email_octopus_id, TEXT, data_key: "id")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def _denormalized_columns
|
|
26
|
+
return [
|
|
27
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
28
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true, converter: :time),
|
|
29
|
+
Webhookdb::Replicator::Column.new(:sent_at, TIMESTAMP, index: true, converter: :time),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:status, TEXT),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:from_name, TEXT, data_key: ["from", "name"]),
|
|
32
|
+
Webhookdb::Replicator::Column.new(:from_email_address, TEXT, data_key: ["from", "email_address"]),
|
|
33
|
+
Webhookdb::Replicator::Column.new(:subject, TEXT),
|
|
34
|
+
Webhookdb::Replicator::Column.new(:row_updated_at, TIMESTAMP, defaulter: :now, optional: true),
|
|
35
|
+
]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _resource_and_event(request)
|
|
39
|
+
return request.body, nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def _update_where_expr
|
|
43
|
+
return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def _timestamp_column_name
|
|
47
|
+
return :row_updated_at
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _webhook_response(_request)
|
|
51
|
+
return Webhookdb::WebhookResponse.ok
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def on_dependency_webhook_upsert(_replicator, _payload, *)
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def calculate_backfill_state_machine
|
|
59
|
+
if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
60
|
+
return step
|
|
61
|
+
end
|
|
62
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
63
|
+
# We're using the API Key from the dependency, we don't need to ask for it here
|
|
64
|
+
step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
|
|
65
|
+
#{self._query_help_output}
|
|
66
|
+
)
|
|
67
|
+
return step.completed
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def _fetch_backfill_page(pagination_token, **)
|
|
71
|
+
api_key = self.service_integration.depends_on.replicator.backfill_key!
|
|
72
|
+
limit = Webhookdb::EmailOctopus.page_size
|
|
73
|
+
base_url = "https://emailoctopus.com"
|
|
74
|
+
endpoint_path = pagination_token || "/api/1.6/campaigns?api_key=#{api_key}&limit=#{limit}"
|
|
75
|
+
response = Webhookdb::Http.get(
|
|
76
|
+
base_url + endpoint_path,
|
|
77
|
+
logger: self.logger,
|
|
78
|
+
timeout: Webhookdb::EmailOctopus.http_timeout,
|
|
79
|
+
)
|
|
80
|
+
data = response.parsed_response
|
|
81
|
+
next_page_link = data.dig("paging", "next")
|
|
82
|
+
return data["data"], next_page_link
|
|
83
|
+
end
|
|
84
|
+
end
|