webhookdb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|