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,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/signalwire"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SignalwireMessageV1 < 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: "signalwire_message_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::SignalwireMessageV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "SignalWire Message",
|
|
15
|
+
supports_backfill: true,
|
|
16
|
+
api_docs_url: "https://developer.signalwire.com/compatibility-api/rest/list-all-messages",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _webhook_response(request)
|
|
21
|
+
auth = request.get_header("Authorization")
|
|
22
|
+
if auth.nil? || !auth.match(/^Basic /)
|
|
23
|
+
return Webhookdb::WebhookResponse.new(
|
|
24
|
+
status: 401,
|
|
25
|
+
body: "",
|
|
26
|
+
reason: "challenge",
|
|
27
|
+
headers: {"Content-Type" => "text/plain", "WWW-Authenticate" => 'Basic realm="Webhookdb"'},
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
user_and_pass = Base64.decode64(auth.gsub(/^Basic /, ""))
|
|
31
|
+
if user_and_pass != self.service_integration.webhook_secret
|
|
32
|
+
return Webhookdb::WebhookResponse.new(
|
|
33
|
+
status: 401,
|
|
34
|
+
body: "",
|
|
35
|
+
reason: "invalid",
|
|
36
|
+
headers: {"Content-Type" => "text/plain"},
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
return Webhookdb::WebhookResponse.new(
|
|
40
|
+
status: 202,
|
|
41
|
+
headers: {"Content-Type" => "text/xml"},
|
|
42
|
+
body: "<Response></Response>",
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def calculate_backfill_state_machine
|
|
47
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
48
|
+
unless self.service_integration.api_url.present?
|
|
49
|
+
step.output = %(Let's finish setting up your SignalWire Messaging (SMS) integration.
|
|
50
|
+
|
|
51
|
+
Rather than using your phone number's webhooks (of which each number can have only one),
|
|
52
|
+
we poll SignalWire for changes, and will also backfill historical messages.
|
|
53
|
+
|
|
54
|
+
To do this, we need your Space URL, Project ID, and an API Token.
|
|
55
|
+
|
|
56
|
+
First enter your Space URL. You can see this on your SignalWire dashboard.
|
|
57
|
+
It's the part of your dashboard URL before '.signalwire.com'.)
|
|
58
|
+
return step.prompting("Space URL").api_url(self.service_integration)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless self.service_integration.backfill_key.present?
|
|
62
|
+
step.output = %(You can get your Project ID from the 'API' section of your SignalWire dashboard.
|
|
63
|
+
|
|
64
|
+
Go to https://#{self.service_integration.api_url}.signalwire.com/credentials and copy your Project ID.)
|
|
65
|
+
return step.prompting("Project ID").backfill_key(self.service_integration)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless self.service_integration.backfill_secret.present?
|
|
69
|
+
step.needs_input = true
|
|
70
|
+
step.output = %(Let's create or reuse an API token. Press the 'New' button on your dashboard,
|
|
71
|
+
name the token something like 'WebhookDB', and under Scopes, ensure the 'Messaging' checkbox is checked.
|
|
72
|
+
Then press 'Save'.
|
|
73
|
+
|
|
74
|
+
Press 'Show' next to the newly-created API token, and copy it.)
|
|
75
|
+
return step.secret_prompt("API Token").backfill_secret(self.service_integration)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
79
|
+
self.service_integration.replicator.clear_backfill_information
|
|
80
|
+
step.output = result.message
|
|
81
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
step.output = %(We are going to start replicating your SignalWire Messages, and will keep it updated.
|
|
85
|
+
#{self._query_help_output}
|
|
86
|
+
)
|
|
87
|
+
return step.completed
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def _verify_backfill_401_err_msg
|
|
91
|
+
return "It looks like that API Key is invalid. Please reenter the API Key you just created:"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def _verify_backfill_err_msg
|
|
95
|
+
return "An error occurred. Please reenter the API Key you just created:"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def _remote_key_column
|
|
99
|
+
return Webhookdb::Replicator::Column.new(:signalwire_id, TEXT, data_key: "sid")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def _denormalized_columns
|
|
103
|
+
return [
|
|
104
|
+
Webhookdb::Replicator::Column.new(
|
|
105
|
+
:date_created,
|
|
106
|
+
TIMESTAMP,
|
|
107
|
+
index: true,
|
|
108
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
109
|
+
),
|
|
110
|
+
Webhookdb::Replicator::Column.new(
|
|
111
|
+
:date_sent,
|
|
112
|
+
TIMESTAMP,
|
|
113
|
+
index: true,
|
|
114
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
115
|
+
),
|
|
116
|
+
Webhookdb::Replicator::Column.new(
|
|
117
|
+
:date_updated,
|
|
118
|
+
TIMESTAMP,
|
|
119
|
+
index: true,
|
|
120
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
121
|
+
),
|
|
122
|
+
Webhookdb::Replicator::Column.new(:direction, TEXT),
|
|
123
|
+
Webhookdb::Replicator::Column.new(:from, TEXT, index: true),
|
|
124
|
+
Webhookdb::Replicator::Column.new(:status, TEXT),
|
|
125
|
+
Webhookdb::Replicator::Column.new(:to, TEXT, index: true),
|
|
126
|
+
]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def _timestamp_column_name
|
|
130
|
+
return :date_updated
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def _resource_and_event(request)
|
|
134
|
+
return request.body, nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def _update_where_expr
|
|
138
|
+
return self.qualified_table_sequel_identifier[:date_updated] < Sequel[:excluded][:date_updated]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def _fetch_backfill_page(pagination_token, last_backfilled:)
|
|
142
|
+
url = "https://#{self.service_integration.api_url}.signalwire.com"
|
|
143
|
+
if pagination_token.blank?
|
|
144
|
+
date_send_max = Date.tomorrow
|
|
145
|
+
url += "/2010-04-01/Accounts/#{self.service_integration.backfill_key}/Messages.json" \
|
|
146
|
+
"?PageSize=100&DateSend%3C=#{date_send_max}"
|
|
147
|
+
else
|
|
148
|
+
url += pagination_token
|
|
149
|
+
end
|
|
150
|
+
response = Webhookdb::Http.get(
|
|
151
|
+
url,
|
|
152
|
+
basic_auth: {username: self.service_integration.backfill_key,
|
|
153
|
+
password: self.service_integration.backfill_secret,},
|
|
154
|
+
logger: self.logger,
|
|
155
|
+
timeout: Webhookdb::Signalwire.http_timeout,
|
|
156
|
+
)
|
|
157
|
+
data = response.parsed_response
|
|
158
|
+
messages = data["messages"]
|
|
159
|
+
|
|
160
|
+
if last_backfilled.present?
|
|
161
|
+
earliest_data_created = messages.empty? ? Time.at(0) : messages[-1].fetch("date_created")
|
|
162
|
+
paged_to_already_seen_records = earliest_data_created < last_backfilled
|
|
163
|
+
|
|
164
|
+
return messages, nil if paged_to_already_seen_records
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
return messages, data["next_page_uri"]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/sponsy_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SponsyCustomerV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::SponsyV1Mixin
|
|
8
|
+
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "sponsy_customer_v1",
|
|
12
|
+
ctor: self,
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Sponsy Customer",
|
|
15
|
+
dependency_descriptor: Webhookdb::Replicator::SponsySlotV1.descriptor,
|
|
16
|
+
supports_backfill: true,
|
|
17
|
+
api_docs_url: "https://api.getsponsy.com/docs",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _denormalized_columns
|
|
22
|
+
return [
|
|
23
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
24
|
+
Webhookdb::Replicator::Column.new(:logo, TEXT, optional: true),
|
|
25
|
+
Webhookdb::Replicator::Column.new(:notes, TEXT, optional: true),
|
|
26
|
+
Webhookdb::Replicator::Column.new(:portal_text, TEXT, data_key: "portalText", optional: true),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:portal_id, TEXT, data_key: "portalId", index: true, optional: true),
|
|
28
|
+
].concat(self._ts_columns)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _backfillers
|
|
32
|
+
return [Backfiller.new(service: self)]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Backfiller < Webhookdb::Backfiller
|
|
36
|
+
def initialize(service:)
|
|
37
|
+
@service = service
|
|
38
|
+
@slot_service = service.service_integration.depends_on.replicator
|
|
39
|
+
super()
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def handle_item(body)
|
|
43
|
+
@service.upsert_webhook_body(body)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fetch_backfill_page(_pagination_token, last_backfilled:)
|
|
47
|
+
customers = @slot_service.admin_dataset do |ds|
|
|
48
|
+
(ds = ds.where { updated_at > last_backfilled }) if last_backfilled
|
|
49
|
+
ds.select_map(Sequel.pg_json(:data)["customer"].as(:customer))
|
|
50
|
+
end
|
|
51
|
+
return customers, nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/sponsy_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SponsyPlacementV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::SponsyV1Mixin
|
|
8
|
+
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "sponsy_placement_v1",
|
|
12
|
+
ctor: self,
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Sponsy Placement",
|
|
15
|
+
dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
|
|
16
|
+
supports_backfill: true,
|
|
17
|
+
api_docs_url: "https://api.getsponsy.com/docs",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _denormalized_columns
|
|
22
|
+
return [
|
|
23
|
+
Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
|
|
24
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
25
|
+
Webhookdb::Replicator::Column.new(:slug, TEXT),
|
|
26
|
+
Webhookdb::Replicator::Column.new(:color, TEXT),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:order, INTEGER),
|
|
28
|
+
].concat(self._ts_columns)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _backfillers(publication_ids: nil, publication_slugs: nil)
|
|
32
|
+
return self._publication_backfillers("/placements", publication_ids:, publication_slugs:)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/sponsy_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SponsyPublicationV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::SponsyV1Mixin
|
|
8
|
+
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "sponsy_publication_v1",
|
|
12
|
+
ctor: self,
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Sponsy Publication",
|
|
15
|
+
supports_backfill: true,
|
|
16
|
+
api_docs_url: "https://api.getsponsy.com/docs",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _denormalized_columns
|
|
21
|
+
col = Webhookdb::Replicator::Column
|
|
22
|
+
return [
|
|
23
|
+
col.new(:name, TEXT),
|
|
24
|
+
col.new(:slug, TEXT),
|
|
25
|
+
col.new(:type, TEXT),
|
|
26
|
+
col.new(:deleted_at, TIMESTAMP, optional: true),
|
|
27
|
+
col.new(
|
|
28
|
+
:days,
|
|
29
|
+
INTEGER_ARRAY,
|
|
30
|
+
converter: col.converter_map_lookup(
|
|
31
|
+
array: true,
|
|
32
|
+
# 'MONDAY' => 0, 0 defaults to 0
|
|
33
|
+
map: col::DAYS_OF_WEEK.rotate.each_with_index.to_h { |dow, idx| [dow, idx] },
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
col.new(
|
|
37
|
+
:days_normalized,
|
|
38
|
+
INTEGER_ARRAY,
|
|
39
|
+
data_key: "days",
|
|
40
|
+
converter: col.converter_map_lookup(
|
|
41
|
+
array: true,
|
|
42
|
+
# 'MONDAY' => 1, 0 => 1
|
|
43
|
+
map: col::DAYS_OF_WEEK.each_with_index.to_a.concat((0..6).zip((0..6).to_a.rotate)).to_h,
|
|
44
|
+
),
|
|
45
|
+
backfill_statement: Sequel.lit(<<~SQL),
|
|
46
|
+
CREATE OR REPLACE FUNCTION pg_temp.sponsy_publication_v1_normalize_days(integer[])
|
|
47
|
+
RETURNS integer[] AS 'SELECT ARRAY(SELECT ((n + 1) % 7) FROM unnest($1) AS n)' LANGUAGE sql IMMUTABLE
|
|
48
|
+
SQL
|
|
49
|
+
backfill_expr: Sequel.lit("pg_temp.sponsy_publication_v1_normalize_days(days)"),
|
|
50
|
+
),
|
|
51
|
+
col.new(
|
|
52
|
+
:day_names,
|
|
53
|
+
TEXT_ARRAY,
|
|
54
|
+
data_key: "days",
|
|
55
|
+
converter: col.converter_map_lookup(
|
|
56
|
+
array: true,
|
|
57
|
+
# 0 => 'MONDAY'
|
|
58
|
+
map: col::DAYS_OF_WEEK.rotate.each_with_index.to_h { |dow, idx| [idx, dow] },
|
|
59
|
+
),
|
|
60
|
+
# Big switch statement to map dow number to name
|
|
61
|
+
backfill_statement: Sequel.lit(<<~SQL),
|
|
62
|
+
CREATE OR REPLACE FUNCTION pg_temp.sponsy_publication_v1_day_names(integer[])
|
|
63
|
+
RETURNS text[] AS 'SELECT ARRAY(SELECT (CASE WHEN n = 0 THEN ''MONDAY'' WHEN n = 1 THEN ''TUESDAY'' WHEN n = 2 THEN ''WEDNESDAY'' WHEN n = 3 THEN ''THURSDAY'' WHEN n = 4 THEN ''FRIDAY'' WHEN n = 5 THEN ''SATURDAY'' WHEN n = 6 THEN ''SUNDAY'' END) FROM unnest($1) AS n)' LANGUAGE sql IMMUTABLE
|
|
64
|
+
SQL
|
|
65
|
+
backfill_expr: Sequel.lit("pg_temp.sponsy_publication_v1_day_names(days)"),
|
|
66
|
+
),
|
|
67
|
+
].concat(self._ts_columns)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def _backfillers
|
|
71
|
+
return [Backfiller.new(self)]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def _fetch_backfill_page(pagination_token, last_backfilled:)
|
|
75
|
+
return self.fetch_sponsy_page("/v1/publications", pagination_token, last_backfilled)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def calculate_backfill_state_machine
|
|
79
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
80
|
+
unless self.service_integration.backfill_secret.present?
|
|
81
|
+
step.needs_input = true
|
|
82
|
+
step.output = %(Great! Let's work on your Sponsy Publications integration.
|
|
83
|
+
|
|
84
|
+
Head over to your Sponsy dashboard and copy your API key:
|
|
85
|
+
|
|
86
|
+
https://getsponsy.com/settings/workspace
|
|
87
|
+
)
|
|
88
|
+
return step.secret_prompt("API key").backfill_secret(self.service_integration)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
92
|
+
self.service_integration.replicator.clear_backfill_information
|
|
93
|
+
step.output = result.message
|
|
94
|
+
return step.secret_prompt("API Key").backfill_secret(self.service_integration)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
step.output = %(We are going to start replicating your Sponsy Publications
|
|
98
|
+
and will keep them updated. You can can also add more Sponsy integrations.
|
|
99
|
+
Run `webhookdb services list` to see what's available.
|
|
100
|
+
#{self._query_help_output}
|
|
101
|
+
)
|
|
102
|
+
return step.completed
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def _verify_backfill_401_err_msg
|
|
106
|
+
return "It looks like that API Key is invalid. Head back to https://getsponsy.com/settings/workspace, " \
|
|
107
|
+
"copy the API key, and try again:"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Normal backfiller that keeps track of inserted items,
|
|
111
|
+
# and marks anything not backfilled as deleted.
|
|
112
|
+
class Backfiller < Webhookdb::Replicator::Base::ServiceBackfiller
|
|
113
|
+
def handle_item(item)
|
|
114
|
+
super
|
|
115
|
+
@seen_ids ||= []
|
|
116
|
+
@seen_ids << item.fetch("id")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def flush_pending_inserts
|
|
120
|
+
self.svc.admin_dataset do |ds|
|
|
121
|
+
ds.exclude(sponsy_id: @seen_ids).where(deleted_at: nil).update(deleted_at: Sequel.function(:now))
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/sponsy_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SponsySlotV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::SponsyV1Mixin
|
|
8
|
+
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "sponsy_slot_v1",
|
|
12
|
+
ctor: self,
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Sponsy Slot",
|
|
15
|
+
dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
|
|
16
|
+
supports_backfill: true,
|
|
17
|
+
api_docs_url: "https://api.getsponsy.com/docs",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _denormalized_columns
|
|
22
|
+
return [
|
|
23
|
+
Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
|
|
24
|
+
Webhookdb::Replicator::Column.new(:date, DATE, index: true),
|
|
25
|
+
Webhookdb::Replicator::Column.new(:notes, TEXT),
|
|
26
|
+
Webhookdb::Replicator::Column.new(
|
|
27
|
+
:customer_id, TEXT, data_key: ["customer", "id"], optional: true, index: true,
|
|
28
|
+
),
|
|
29
|
+
Webhookdb::Replicator::Column.new(
|
|
30
|
+
:placement_id, TEXT, data_key: ["placement", "id"], optional: true, index: true,
|
|
31
|
+
),
|
|
32
|
+
Webhookdb::Replicator::Column.new(
|
|
33
|
+
:status_id, TEXT, data_key: ["status", "id"], optional: true, index: true,
|
|
34
|
+
),
|
|
35
|
+
].concat(self._ts_columns)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _backfillers(publication_ids: nil, publication_slugs: nil)
|
|
39
|
+
return self._publication_backfillers("/slots", publication_ids:, publication_slugs:)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/sponsy_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::SponsyStatusV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::SponsyV1Mixin
|
|
8
|
+
|
|
9
|
+
def self.descriptor
|
|
10
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
11
|
+
name: "sponsy_status_v1",
|
|
12
|
+
ctor: self,
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Sponsy Status",
|
|
15
|
+
resource_name_plural: "Sponsy Statuses",
|
|
16
|
+
dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
|
|
17
|
+
supports_backfill: true,
|
|
18
|
+
api_docs_url: "https://api.getsponsy.com/docs",
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def _denormalized_columns
|
|
23
|
+
return [
|
|
24
|
+
Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
|
|
25
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
26
|
+
Webhookdb::Replicator::Column.new(:slug, TEXT),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:color, TEXT),
|
|
28
|
+
Webhookdb::Replicator::Column.new(:order, INTEGER),
|
|
29
|
+
].concat(self._ts_columns)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def _backfillers(publication_ids: nil, publication_slugs: nil)
|
|
33
|
+
return self._publication_backfillers("/status", publication_ids:, publication_slugs:)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/sponsy"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Replicator::SponsyV1Mixin
|
|
6
|
+
include Webhookdb::DBAdapter::ColumnTypes
|
|
7
|
+
|
|
8
|
+
def _remote_key_column
|
|
9
|
+
return Webhookdb::Replicator::Column.new(:sponsy_id, TEXT, data_key: "id")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def _timestamp_column_name
|
|
13
|
+
return :updated_at
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def _ts_columns
|
|
17
|
+
return [
|
|
18
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "createdAt"),
|
|
19
|
+
Webhookdb::Replicator::Column.new(
|
|
20
|
+
:updated_at, TIMESTAMP,
|
|
21
|
+
data_key: "updatedAt",
|
|
22
|
+
defaulter: Webhookdb::Replicator::Column.defaulter_from_resource_field(:created_at),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _verify_backfill_err_msg
|
|
28
|
+
return "Looks like your API key is invalid."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def api_url
|
|
32
|
+
return "https://api.getsponsy.com"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def auth_headers
|
|
36
|
+
return {"X-Api-Key" => self.find_api_key}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def root_integration
|
|
40
|
+
return @root_integration ||= Webhookdb::Replicator.find_at_root!(self.service_integration,
|
|
41
|
+
service_name: "sponsy_publication_v1",)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_api_key
|
|
45
|
+
return self.root_integration.backfill_secret
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def _resource_and_event(request)
|
|
49
|
+
return request.body, nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _update_where_expr
|
|
53
|
+
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def _webhook_response(_request)
|
|
57
|
+
# There are no webhooks to respond to, these are backfill-only integrations
|
|
58
|
+
return Webhookdb::WebhookResponse.ok
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
62
|
+
def calculate_backfill_state_machine
|
|
63
|
+
check_dep = self.class.descriptor.dependency_descriptor
|
|
64
|
+
if check_dep && (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
|
|
65
|
+
return step
|
|
66
|
+
end
|
|
67
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
68
|
+
step.output = %(We will start replicating #{self.resource_name_plural} into your WebhookDB database.
|
|
69
|
+
|
|
70
|
+
#{self._query_help_output(prefix: "Once data is available, you can query #{self.resource_name_plural}.")})
|
|
71
|
+
return step.completed
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def on_dependency_webhook_upsert(_replicator, _payload, *)
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def _parallel_backfill = Webhookdb::Sponsy.parallel_backfill
|
|
79
|
+
|
|
80
|
+
# Paginate from most recently updated.
|
|
81
|
+
# We paginate until either:
|
|
82
|
+
# - There are no more pages (the 'after cursor' is nil), or
|
|
83
|
+
# - the updated at timestamp predates the time we last backfilled,
|
|
84
|
+
# meaning we probably already saw this update.
|
|
85
|
+
def fetch_sponsy_page(tail, pagination_token, last_backfilled)
|
|
86
|
+
url = self.api_url + tail
|
|
87
|
+
begin
|
|
88
|
+
response = Webhookdb::Http.get(
|
|
89
|
+
url,
|
|
90
|
+
query: {
|
|
91
|
+
limit: Webhookdb::Sponsy.page_size.to_s,
|
|
92
|
+
afterCursor: pagination_token,
|
|
93
|
+
orderBy: "updatedAt",
|
|
94
|
+
orderDirection: "DESC",
|
|
95
|
+
},
|
|
96
|
+
headers: self.auth_headers,
|
|
97
|
+
logger: self.logger,
|
|
98
|
+
timeout: Webhookdb::Sponsy.http_timeout,
|
|
99
|
+
)
|
|
100
|
+
rescue Webhookdb::Http::Error => e
|
|
101
|
+
raise e unless e.status == 404
|
|
102
|
+
self.logger.warn("sponsy_404", error: e)
|
|
103
|
+
return [], nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
data = response.parsed_response.fetch("data")
|
|
107
|
+
after_cursor = response.parsed_response.fetch("cursor", {}).fetch("afterCursor", nil)
|
|
108
|
+
return data, nil if after_cursor.nil?
|
|
109
|
+
return [], nil if data.empty?
|
|
110
|
+
last_updated = data.last.fetch("updatedAt")
|
|
111
|
+
return data, nil if last_updated < (last_backfilled || Time.at(0))
|
|
112
|
+
return data, after_cursor
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _publication_backfillers(tail, publication_ids: nil, publication_slugs: nil)
|
|
116
|
+
raise Webhookdb::Replicator::CredentialsMissing, "This Sponsy integration is missing a dependency with auth" if
|
|
117
|
+
self.find_api_key.blank?
|
|
118
|
+
|
|
119
|
+
publications_svc = self.service_integration.depends_on.replicator
|
|
120
|
+
backfillers = publications_svc.readonly_dataset(timeout: :fast) do |pub_ds|
|
|
121
|
+
pub_ds = Webhookdb::Dbutil.reduce_expr(
|
|
122
|
+
pub_ds,
|
|
123
|
+
:|,
|
|
124
|
+
[publication_ids && Sequel[sponsy_id: publication_ids], publication_slugs && Sequel[slug: publication_slugs]],
|
|
125
|
+
)
|
|
126
|
+
pub_ds = pub_ds.where(deleted_at: nil)
|
|
127
|
+
pub_ds.select(:sponsy_id).map do |publication|
|
|
128
|
+
PublicationChildBackfiller.new(
|
|
129
|
+
service: self,
|
|
130
|
+
publication_id: publication.fetch(:sponsy_id),
|
|
131
|
+
tail:,
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
return backfillers
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class PublicationChildBackfiller < Webhookdb::Backfiller
|
|
139
|
+
include Webhookdb::Backfiller::Bulk
|
|
140
|
+
|
|
141
|
+
attr_reader :upserting_replicator
|
|
142
|
+
|
|
143
|
+
def initialize(service:, publication_id:, tail:)
|
|
144
|
+
@service = service
|
|
145
|
+
@upserting_replicator = @service
|
|
146
|
+
@publication_id = publication_id
|
|
147
|
+
@tail = tail
|
|
148
|
+
super()
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def upsert_page_size = 500
|
|
152
|
+
def conditional_upsert? = true
|
|
153
|
+
|
|
154
|
+
def prepare_body(body)
|
|
155
|
+
body["publication_id"] = @publication_id
|
|
156
|
+
body
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def fetch_backfill_page(pagination_token, last_backfilled:)
|
|
160
|
+
return @service.fetch_sponsy_page(
|
|
161
|
+
"/v1/publications/#{@publication_id}#{@tail}", pagination_token, last_backfilled,
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::Replicator::StateMachineStep
|
|
4
|
+
attr_accessor :needs_input,
|
|
5
|
+
:prompt,
|
|
6
|
+
:prompt_is_secret,
|
|
7
|
+
:post_to_url,
|
|
8
|
+
:complete,
|
|
9
|
+
:output,
|
|
10
|
+
:error_code,
|
|
11
|
+
:post_params,
|
|
12
|
+
:post_params_value_key
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@needs_input = false
|
|
16
|
+
@prompt = ""
|
|
17
|
+
@prompt_is_secret = false
|
|
18
|
+
@post_to_url = ""
|
|
19
|
+
@complete = false
|
|
20
|
+
@output = ""
|
|
21
|
+
@error_code = ""
|
|
22
|
+
@post_params = {}
|
|
23
|
+
@post_params_value_key = "value"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def successful? = return self.complete && self.error_code.blank?
|
|
27
|
+
|
|
28
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
29
|
+
def completed
|
|
30
|
+
self.complete = true
|
|
31
|
+
self.needs_input = false
|
|
32
|
+
return self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
36
|
+
def secret_prompt(field)
|
|
37
|
+
return self.prompting(field, secret: true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def prompting(field, secret: false)
|
|
41
|
+
return self.set_prompt("Paste or type your #{field} here:", secret:)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def set_prompt(value, secret: false)
|
|
45
|
+
self.needs_input = true
|
|
46
|
+
self.prompt = value
|
|
47
|
+
self.prompt_is_secret = secret
|
|
48
|
+
self.complete = false
|
|
49
|
+
return self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
53
|
+
def backfill_secret(sint) = self.transition_field(sint, "backfill_secret")
|
|
54
|
+
|
|
55
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
56
|
+
def backfill_key(sint) = self.transition_field(sint, "backfill_key")
|
|
57
|
+
|
|
58
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
59
|
+
def webhook_secret(sint) = self.transition_field(sint, "webhook_secret")
|
|
60
|
+
|
|
61
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
62
|
+
def api_url(sint) = self.transition_field(sint, "api_url")
|
|
63
|
+
|
|
64
|
+
# @return [Webhookdb::Replicator::StateMachineStep]
|
|
65
|
+
def transition_field(sint, field)
|
|
66
|
+
self.post_to_url = sint.authed_api_path + "/transition/#{field}"
|
|
67
|
+
return self
|
|
68
|
+
end
|
|
69
|
+
end
|