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,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/plivo"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::PlivoSmsInboundV1 < 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: "plivo_sms_inbound_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::PlivoSmsInboundV1.new(sint) },
|
|
13
|
+
feature_roles: ["beta"],
|
|
14
|
+
resource_name_singular: "Plivo Inbound SMS Message",
|
|
15
|
+
supports_webhooks: true,
|
|
16
|
+
api_docs_url: "https://www.plivo.com/docs/sms/api/message#the-message-object",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _remote_key_column
|
|
21
|
+
return Webhookdb::Replicator::Column.new(:plivo_message_uuid, TEXT, data_key: "MessageUUID")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _denormalized_columns
|
|
25
|
+
return [
|
|
26
|
+
Webhookdb::Replicator::Column.new(:row_inserted_at, TIMESTAMP, defaulter: :now, optional: true, index: true),
|
|
27
|
+
Webhookdb::Replicator::Column.new(:from_number, TEXT, data_key: "From", index: true),
|
|
28
|
+
Webhookdb::Replicator::Column.new(:to_number, TEXT, data_key: "To", index: true),
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def _timestamp_column_name
|
|
33
|
+
return :row_inserted_at
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def _update_where_expr
|
|
37
|
+
# These are immutable events, not updates, so never update after inserted.
|
|
38
|
+
return Sequel[false]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def _webhook_response(request)
|
|
42
|
+
return Webhookdb::Plivo.webhook_response(request, self.service_integration.backfill_secret)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _resource_and_event(request)
|
|
46
|
+
body = request.body
|
|
47
|
+
raise Webhookdb::InvalidPrecondition, "body should be form-encoded string" unless body.is_a?(String)
|
|
48
|
+
resource = URI.decode_www_form(body).to_h
|
|
49
|
+
return resource, nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
INTEGER_KEYS = ["TotalAmount", "TotalRate", "Units"].freeze
|
|
53
|
+
|
|
54
|
+
def _resource_to_data(resource, *)
|
|
55
|
+
super
|
|
56
|
+
h = resource.dup
|
|
57
|
+
INTEGER_KEYS.each do |k|
|
|
58
|
+
h[k] = h[k].to_i if h.key?(k)
|
|
59
|
+
end
|
|
60
|
+
return h
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def calculate_webhook_state_machine
|
|
64
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
65
|
+
if self.service_integration.backfill_key.blank?
|
|
66
|
+
step.output = %(You are about to set up an endpoint to receive #{self.resource_name_plural}.
|
|
67
|
+
You'll need to give us your Auth ID and Auth Token so we can validate webhooks.
|
|
68
|
+
Once that is set up, we'll help you set up your WebhookDB endpoint in Plivo.)
|
|
69
|
+
return step.secret_prompt("Auth ID").backfill_key(self.service_integration)
|
|
70
|
+
end
|
|
71
|
+
if self.service_integration.backfill_secret.blank?
|
|
72
|
+
return step.secret_prompt("Auth Token").backfill_secret(self.service_integration)
|
|
73
|
+
end
|
|
74
|
+
begin
|
|
75
|
+
Webhookdb::Plivo.request(
|
|
76
|
+
:get,
|
|
77
|
+
"/",
|
|
78
|
+
auth_id: self.service_integration.backfill_key,
|
|
79
|
+
auth_token: self.service_integration.backfill_secret,
|
|
80
|
+
timeout: Webhookdb::Plivo.http_timeout,
|
|
81
|
+
)
|
|
82
|
+
rescue Webhookdb::Http::Error => e
|
|
83
|
+
self.service_integration.update(backfill_key: "", backfill_secret: "")
|
|
84
|
+
step.output = %(Those credentials didn't work (Plivo returned an HTTP #{e.status} error).
|
|
85
|
+
Let's start over with your Auth ID (it probably begins with an MA or SA).)
|
|
86
|
+
return step.secret_prompt("Auth ID").backfill_key(self.service_integration)
|
|
87
|
+
end
|
|
88
|
+
step.output = %(Perfect, those credentials check out.
|
|
89
|
+
You can use this endpoint in your Plivo Application to receive webhooks:
|
|
90
|
+
|
|
91
|
+
#{self._webhook_endpoint}
|
|
92
|
+
|
|
93
|
+
This can be done through the UI, or the API by creating or updating an Application under your Account.
|
|
94
|
+
|
|
95
|
+
As messages comes in, they'll be upserted into your table.
|
|
96
|
+
#{self._query_help_output}
|
|
97
|
+
|
|
98
|
+
You can also use `webhookdb httpsync` to set up notifications to your own server
|
|
99
|
+
when rows are modified.)
|
|
100
|
+
return step.completed
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postmark"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::PostmarkInboundMessageV1 < 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: "postmark_inbound_message_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::PostmarkInboundMessageV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Postmark Inbound Message",
|
|
15
|
+
supports_webhooks: true,
|
|
16
|
+
api_docs_url: "https://postmarkapp.com/developer/user-guide/inbound/parse-an-email",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _remote_key_column
|
|
21
|
+
return Webhookdb::Replicator::Column.new(:message_id, TEXT, data_key: "MessageID")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _denormalized_columns
|
|
25
|
+
col = Webhookdb::Replicator::Column
|
|
26
|
+
return [
|
|
27
|
+
col.new(:from_email, TEXT, index: true, data_key: ["FromFull", "Email"]),
|
|
28
|
+
col.new(:to_email, TEXT, index: true, data_key: ["ToFull", 0, "Email"]),
|
|
29
|
+
col.new(:subject, TEXT, index: true, data_key: "Subject"),
|
|
30
|
+
col.new(:timestamp, TIMESTAMP, index: true, data_key: "Date"),
|
|
31
|
+
col.new(:tag, TEXT, index: true, data_key: "Tag"),
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _prepare_for_insert(*)
|
|
36
|
+
h = super
|
|
37
|
+
ts_str = h[:timestamp]
|
|
38
|
+
# We get some weird time formats, like 'Wed, 05 Jul 2023 22:27:31 +0000 (UTC)'.
|
|
39
|
+
# Ruby can parse these, but PG cannot, so sanitize the ' (UTC)' out of here.
|
|
40
|
+
# Depending on what other random stuff we see, we can make this more general later.
|
|
41
|
+
h[:timestamp] = ts_str.gsub(/ \(UTC\)$/, "")
|
|
42
|
+
return h
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _timestamp_column_name
|
|
46
|
+
return :timestamp
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _resource_and_event(request)
|
|
50
|
+
return request.body, nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def _update_where_expr
|
|
54
|
+
# These are immutable events, not updates, so never update after inserted.
|
|
55
|
+
return Sequel[false]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def _webhook_response(request)
|
|
59
|
+
return Webhookdb::Postmark.webhook_response(request)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def calculate_webhook_state_machine
|
|
63
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
64
|
+
if self.service_integration.webhook_secret.blank?
|
|
65
|
+
step.output = %(You are about to set up webhooks for Postmark Inbound Messages.
|
|
66
|
+
When emails are sent to the email address configured in Postmark,
|
|
67
|
+
they will show up in WebhookDB automatically.
|
|
68
|
+
|
|
69
|
+
1. Go to https://account.postmarkapp.com/servers
|
|
70
|
+
2. Choose the server you want to replicator.
|
|
71
|
+
3. Choose the Inbound Stream to replicate.
|
|
72
|
+
2. Go to the 'Settings' tab.
|
|
73
|
+
3. Use this Webhook URL: #{self.webhook_endpoint}
|
|
74
|
+
5. Hit 'Check' and verify it works. If it does not, double check your settings.
|
|
75
|
+
6. Hit 'Save Webhook'.)
|
|
76
|
+
step.set_prompt("Press Enter after Save Webhook succeeds:")
|
|
77
|
+
step.transition_field(self.service_integration, "noop_create")
|
|
78
|
+
self.service_integration.update(webhook_secret: "placeholder")
|
|
79
|
+
return step
|
|
80
|
+
end
|
|
81
|
+
step.output = %(
|
|
82
|
+
All set! Inbound Messages will be synced as they come in.
|
|
83
|
+
|
|
84
|
+
#{self._query_help_output})
|
|
85
|
+
return step.completed
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def backfill_not_supported_message
|
|
89
|
+
return %(We don't yet support backfilling Postmark Inbound Messages.
|
|
90
|
+
File an issue at #{Webhookdb.oss_repo_url} or email hello@webhookdb.com to let us know if this is something you want!
|
|
91
|
+
|
|
92
|
+
Run `webhookdb integration reset #{self.service_integration.opaque_id}` to go through webhook setup.)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postmark"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::PostmarkOutboundMessageEventV1 < 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: "postmark_outbound_message_event_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::PostmarkOutboundMessageEventV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Postmark Outbound Message Event",
|
|
15
|
+
supports_webhooks: true,
|
|
16
|
+
api_docs_url: "https://postmarkapp.com/developer/webhooks/webhooks-overview",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
TIMESTAMP_KEYS = ["ReceivedAt", "DeliveredAt", "BouncedAt", "ChangedAt"].freeze
|
|
21
|
+
|
|
22
|
+
LOOKUP_TIMESTAMP = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
23
|
+
ruby: lambda do |resource:, **|
|
|
24
|
+
tskey = TIMESTAMP_KEYS.find { |k| resource[k] }
|
|
25
|
+
raise KeyError, "Cannot find valid timestamp key in #{resource}" if tskey.nil?
|
|
26
|
+
resource[tskey]
|
|
27
|
+
end,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
BUILD_EVENT_MD5 = Webhookdb::Replicator::Column::IsomorphicProc.new(
|
|
31
|
+
ruby: lambda do |resource:, **|
|
|
32
|
+
md5 = Digest::MD5.new
|
|
33
|
+
md5.update(resource.fetch("MessageID"))
|
|
34
|
+
md5.update(resource.fetch("RecordType"))
|
|
35
|
+
md5.update(LOOKUP_TIMESTAMP.ruby.call(resource:))
|
|
36
|
+
md5.hexdigest
|
|
37
|
+
end,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def _remote_key_column
|
|
41
|
+
return Webhookdb::Replicator::Column.new(:event_id, UUID, optional: true, defaulter: BUILD_EVENT_MD5)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _denormalized_columns
|
|
45
|
+
col = Webhookdb::Replicator::Column
|
|
46
|
+
return [
|
|
47
|
+
col.new(:message_id, TEXT, index: true, data_key: "MessageID"),
|
|
48
|
+
col.new(:timestamp, TIMESTAMP, index: true, optional: true, defaulter: LOOKUP_TIMESTAMP),
|
|
49
|
+
col.new(:record_type, TEXT, index: true, optional: true, data_key: "RecordType"),
|
|
50
|
+
col.new(:tag, TEXT, index: true, optional: true, data_key: "Tag"),
|
|
51
|
+
col.new(:recipient, TEXT, index: true, optional: true, data_key: "Recipient"),
|
|
52
|
+
col.new(:changed_at, TIMESTAMP, index: true, optional: true, data_key: "ChangedAt"),
|
|
53
|
+
col.new(:delivered_at, TIMESTAMP, index: true, optional: true, data_key: "DeliveredAt"),
|
|
54
|
+
col.new(:received_at, TIMESTAMP, index: true, optional: true, data_key: "ReceivedAt"),
|
|
55
|
+
col.new(:bounced_at, TIMESTAMP, index: true, optional: true, data_key: "BouncedAt"),
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def _timestamp_column_name
|
|
60
|
+
return :timestamp
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def _resource_and_event(request)
|
|
64
|
+
return request.body, nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def _update_where_expr
|
|
68
|
+
# Since the primary key is based on the timestamp, we never do updates
|
|
69
|
+
return Sequel[false]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def _webhook_response(request)
|
|
73
|
+
return Webhookdb::Postmark.webhook_response(request)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def calculate_webhook_state_machine
|
|
77
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
78
|
+
if self.service_integration.webhook_secret.blank?
|
|
79
|
+
step.output = %(You are about to set up webhooks for Postmark Outbound Message Events,
|
|
80
|
+
like deliveries, clicks, and opens.
|
|
81
|
+
|
|
82
|
+
1. In the Postmark UI, locate the server and stream (Transactional or Broadcast) you want to record.
|
|
83
|
+
2. Click on the 'Webhooks' tab.
|
|
84
|
+
3. Use this Webhook URL: #{self.webhook_endpoint}
|
|
85
|
+
4. Check the events you want to send.
|
|
86
|
+
5. Hit 'Send test' and verify it works. If it does not, double check your settings.
|
|
87
|
+
6. Hit 'Save Changes')
|
|
88
|
+
step.set_prompt("Press Enter after Saved Changes succeeds:")
|
|
89
|
+
step.transition_field(self.service_integration, "noop_create")
|
|
90
|
+
self.service_integration.update(webhook_secret: "placeholder")
|
|
91
|
+
return step
|
|
92
|
+
end
|
|
93
|
+
step.output = %(
|
|
94
|
+
All set! Events will be synced as they come in.
|
|
95
|
+
|
|
96
|
+
#{self._query_help_output})
|
|
97
|
+
return step.completed
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def backfill_not_supported_message
|
|
101
|
+
return %(We don't yet support backfilling Postmark Outbound Message Events.
|
|
102
|
+
|
|
103
|
+
File an issue at #{Webhookdb.oss_repo_url} or email hello@webhookdb.com to let us know if this is something you want!
|
|
104
|
+
|
|
105
|
+
Run `webhookdb integration reset #{self.service_integration.opaque_id}` to go through webhook setup.)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::Replicator::SchemaModification
|
|
4
|
+
# All of these statements can be sent to the server at once.
|
|
5
|
+
# @return [Array<String>]
|
|
6
|
+
attr_reader :transaction_statements
|
|
7
|
+
# Each of these statements must be executed one-at-a-time.
|
|
8
|
+
# An example would be creating indices concurrently in PG.
|
|
9
|
+
# @return [Array<String>]
|
|
10
|
+
attr_reader :nontransaction_statements
|
|
11
|
+
# Each of these statements are executed in the application database,
|
|
12
|
+
# NOT whatever database the schema modification itself is executed against.
|
|
13
|
+
# @return [Array<String>]
|
|
14
|
+
attr_reader :application_database_statements
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@transaction_statements = []
|
|
18
|
+
@nontransaction_statements = []
|
|
19
|
+
@application_database_statements = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def noop?
|
|
23
|
+
return @transaction_statements.empty? &&
|
|
24
|
+
@nontransaction_statements.empty? &&
|
|
25
|
+
@application_database_statements.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def execute(db)
|
|
29
|
+
db << stmt2str(@transaction_statements)
|
|
30
|
+
@nontransaction_statements.each { |stmt| db << stmt }
|
|
31
|
+
Webhookdb::Postgres::Model.db << stmt2str(@application_database_statements)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def stmt2str(lines)
|
|
35
|
+
return "" if lines.empty?
|
|
36
|
+
return lines.join(";\n") + ";"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
return [stmt2str(@transaction_statements), stmt2str(@nontransaction_statements)].reject(&:blank?).join("\n")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/shopify"
|
|
4
|
+
require "webhookdb/replicator/shopify_v1_mixin"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::Replicator::ShopifyCustomerV1 < Webhookdb::Replicator::Base
|
|
7
|
+
include Appydays::Loggable
|
|
8
|
+
include Webhookdb::Replicator::ShopifyV1Mixin
|
|
9
|
+
|
|
10
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
11
|
+
def self.descriptor
|
|
12
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
13
|
+
name: "shopify_customer_v1",
|
|
14
|
+
ctor: ->(sint) { Webhookdb::Replicator::ShopifyCustomerV1.new(sint) },
|
|
15
|
+
feature_roles: [],
|
|
16
|
+
resource_name_singular: "Shopify Customer",
|
|
17
|
+
supports_webhooks: true,
|
|
18
|
+
supports_backfill: true,
|
|
19
|
+
api_docs_url: "https://shopify.dev/docs/api/admin-rest/2023-10/resources/customer",
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _remote_key_column
|
|
24
|
+
return Webhookdb::Replicator::Column.new(:shopify_id, TEXT, data_key: "id")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _denormalized_columns
|
|
28
|
+
return [
|
|
29
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:email, TEXT, index: true),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:first_name, TEXT),
|
|
32
|
+
Webhookdb::Replicator::Column.new(:last_name, TEXT),
|
|
33
|
+
Webhookdb::Replicator::Column.new(:last_order_id, TEXT),
|
|
34
|
+
Webhookdb::Replicator::Column.new(:last_order_name, TEXT),
|
|
35
|
+
Webhookdb::Replicator::Column.new(:phone, TEXT, index: true),
|
|
36
|
+
Webhookdb::Replicator::Column.new(:state, TEXT),
|
|
37
|
+
Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
|
|
38
|
+
]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def _update_where_expr
|
|
42
|
+
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _mixin_backfill_url
|
|
46
|
+
return "/admin/api/2021-04/customers.json"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _mixin_backfill_hashkey
|
|
50
|
+
return "customers"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def _mixin_backfill_warning
|
|
54
|
+
return %(Shopify allows us to backfill your entire Customer history,
|
|
55
|
+
so you're in good shape.
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/shopify"
|
|
4
|
+
require "webhookdb/replicator/shopify_v1_mixin"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::Replicator::ShopifyOrderV1 < Webhookdb::Replicator::Base
|
|
7
|
+
include Appydays::Loggable
|
|
8
|
+
include Webhookdb::Replicator::ShopifyV1Mixin
|
|
9
|
+
|
|
10
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
11
|
+
def self.descriptor
|
|
12
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
13
|
+
name: "shopify_order_v1",
|
|
14
|
+
ctor: ->(sint) { Webhookdb::Replicator::ShopifyOrderV1.new(sint) },
|
|
15
|
+
feature_roles: [],
|
|
16
|
+
resource_name_singular: "Shopify Order",
|
|
17
|
+
supports_webhooks: true,
|
|
18
|
+
supports_backfill: true,
|
|
19
|
+
api_docs_url: "https://shopify.dev/docs/api/admin-rest/2023-10/resources/order",
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _remote_key_column
|
|
24
|
+
return Webhookdb::Replicator::Column.new(:shopify_id, TEXT, data_key: "id")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _denormalized_columns
|
|
28
|
+
return [
|
|
29
|
+
Webhookdb::Replicator::Column.new(:app_id, TEXT),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:cancelled_at, TIMESTAMP, index: true),
|
|
31
|
+
Webhookdb::Replicator::Column.new(:cart_token, TEXT),
|
|
32
|
+
Webhookdb::Replicator::Column.new(:checkout_token, TEXT),
|
|
33
|
+
Webhookdb::Replicator::Column.new(:closed_at, TIMESTAMP, index: true),
|
|
34
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
|
|
35
|
+
Webhookdb::Replicator::Column.new(:customer_id, TEXT,
|
|
36
|
+
index: true, data_key: ["customer", "id"], optional: true,),
|
|
37
|
+
Webhookdb::Replicator::Column.new(:email, TEXT, index: true),
|
|
38
|
+
Webhookdb::Replicator::Column.new(:name, TEXT),
|
|
39
|
+
Webhookdb::Replicator::Column.new(:order_number, INTEGER, index: true),
|
|
40
|
+
Webhookdb::Replicator::Column.new(:phone, TEXT, index: true),
|
|
41
|
+
Webhookdb::Replicator::Column.new(:token, TEXT),
|
|
42
|
+
Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
|
|
43
|
+
Webhookdb::Replicator::Column.new(:user_id, TEXT, index: true),
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _update_where_expr
|
|
48
|
+
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def _mixin_backfill_url
|
|
52
|
+
return "/admin/api/2021-04/orders.json?status=any"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def _mixin_backfill_hashkey
|
|
56
|
+
return "orders"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def _mixin_backfill_warning
|
|
60
|
+
return %(Note that Shopify only allows access to orders made in the last 60 days,
|
|
61
|
+
so this history will not be comprehensive.
|
|
62
|
+
Please email #{Webhookdb.support_email} if you need a complete history backfill.)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/shopify"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Replicator::ShopifyV1Mixin
|
|
6
|
+
def _mixin_backfill_url
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def _mixin_backfill_hashkey
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def _mixin_backfill_warning
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def _timestamp_column_name
|
|
19
|
+
return :updated_at
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# For Shopify endpoints the object and webhook have the same shape—the webhook is simply the updated object
|
|
23
|
+
def _resource_and_event(request)
|
|
24
|
+
return request.body, nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _webhook_response(request)
|
|
28
|
+
# info for debugging
|
|
29
|
+
shopify_auth = request.env["HTTP_X_SHOPIFY_HMAC_SHA256"]
|
|
30
|
+
log_params = {shopify_auth:, shopify_body: request.params}
|
|
31
|
+
self.logger.debug "webhook hit shopify endpoint", log_params
|
|
32
|
+
|
|
33
|
+
return Webhookdb::WebhookResponse.error("missing hmac") if shopify_auth.nil?
|
|
34
|
+
request.body.rewind
|
|
35
|
+
request_data = request.body.read
|
|
36
|
+
verified = Webhookdb::Shopify.verify_webhook(request_data, shopify_auth, self.service_integration.webhook_secret)
|
|
37
|
+
return Webhookdb::WebhookResponse.ok if verified
|
|
38
|
+
return Webhookdb::WebhookResponse.error("invalid hmac")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_state_change(field, value)
|
|
42
|
+
# special handling for converting a shop name into an api url
|
|
43
|
+
if field == "shop_name"
|
|
44
|
+
# revisionist history
|
|
45
|
+
field = "api_url"
|
|
46
|
+
value = "https://#{value}.myshopify.com"
|
|
47
|
+
end
|
|
48
|
+
return super(field, value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calculate_webhook_state_machine
|
|
52
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
53
|
+
# if the service integration doesn't exist, create it with some standard values
|
|
54
|
+
unless self.service_integration.webhook_secret.present?
|
|
55
|
+
step.needs_input = true
|
|
56
|
+
step.output = %(You are about to start replicating #{self.resource_name_plural} into WebhookDB.
|
|
57
|
+
We've made an endpoint available for #{self.resource_name_singular} webhooks:
|
|
58
|
+
|
|
59
|
+
#{self._webhook_endpoint}
|
|
60
|
+
|
|
61
|
+
From your Shopify admin dashboard, go to Settings -> Notifications.
|
|
62
|
+
Scroll down to the Webhook Section.
|
|
63
|
+
You will need to create a separate webhook for each #{self.resource_name_singular} event,
|
|
64
|
+
but you can use the URL above and select JSON as the desired format for all of them.
|
|
65
|
+
|
|
66
|
+
At the very bottom of the page, you should see a signing secret that will be used to verify all webhooks.
|
|
67
|
+
Copy that value.)
|
|
68
|
+
return step.secret_prompt("secret").webhook_secret(self.service_integration)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
step.output = %(Great! WebhookDB is now listening for #{self.resource_name_singular} webhooks.
|
|
72
|
+
#{self._query_help_output}
|
|
73
|
+
In order to backfill existing #{self.resource_name_plural}, run this from a shell:
|
|
74
|
+
|
|
75
|
+
#{self._backfill_command}
|
|
76
|
+
)
|
|
77
|
+
return step.completed
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def calculate_backfill_state_machine
|
|
81
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
82
|
+
unless self.service_integration.backfill_key.present?
|
|
83
|
+
step.output = \
|
|
84
|
+
%(In order to backfill #{self.resource_name_plural}, we need an API key and password
|
|
85
|
+
(file an issue at #{Webhookdb.oss_repo_url} if you need token support).
|
|
86
|
+
|
|
87
|
+
- From your Shopify Dashboard, go to Apps and click the "Manage Private Apps" link at the bottom of the page.
|
|
88
|
+
- Then click "Create Private App" and fill out the necessary information.
|
|
89
|
+
- When you get to the "Admin API" section,
|
|
90
|
+
select "Read Access" for the #{self.resource_name_singular} API and leave the rest as is.
|
|
91
|
+
- Then hit "Save" and create the app.
|
|
92
|
+
- You'll be presented with a page that has info about your app's credentials.
|
|
93
|
+
|
|
94
|
+
We need both the API Key and Password.)
|
|
95
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
unless self.service_integration.backfill_secret.present?
|
|
99
|
+
return step.secret_prompt("Password").backfill_secret(self.service_integration)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
unless self.service_integration.api_url.present?
|
|
103
|
+
step.output = %(Nice! Now we need the name of your shop so that we can construct the api url.
|
|
104
|
+
This is the name that is used by Shopify for URL purposes.
|
|
105
|
+
It should be in the top left corner of your Admin Dashboard next to the Shopify logo.)
|
|
106
|
+
step.post_to_url = self.service_integration.authed_api_path + "/transition/shop_name"
|
|
107
|
+
return step.prompting("Shop Name")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# we check backfill credentials *after* entering the api_url because it is required to establish the auth connection
|
|
111
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
112
|
+
self.service_integration.replicator.clear_backfill_information
|
|
113
|
+
step.output = result.message
|
|
114
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
step.output = %(Great! We are going to start backfilling your #{self.resource_name_plural}.
|
|
118
|
+
#{self._mixin_backfill_warning}
|
|
119
|
+
#{self._query_help_output})
|
|
120
|
+
return step.completed
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def _verify_backfill_403_err_msg
|
|
124
|
+
return "It looks like that API Key does not have permission to access #{self.resource_name_singular} Records. " \
|
|
125
|
+
"Please check the permissions by going to your private app page and " \
|
|
126
|
+
"looking at the list of active permissions. " \
|
|
127
|
+
"Once you've verified or corrected the permissions for this key, " \
|
|
128
|
+
"please reenter the API Key you just created:"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def _verify_backfill_401_err_msg
|
|
132
|
+
return "It looks like that API Key/Access Token combination is invalid. " \
|
|
133
|
+
"Please reenter the API Key you just created:"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def _verify_backfill_err_msg
|
|
137
|
+
return "An error occurred. Please reenter the API Key you just created:"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def _fetch_backfill_page(pagination_token, **_kwargs)
|
|
141
|
+
url = if pagination_token.blank?
|
|
142
|
+
self.service_integration.api_url + self._mixin_backfill_url
|
|
143
|
+
else
|
|
144
|
+
pagination_token
|
|
145
|
+
end
|
|
146
|
+
response = Webhookdb::Http.get(
|
|
147
|
+
url,
|
|
148
|
+
basic_auth: {username: self.service_integration.backfill_key,
|
|
149
|
+
password: self.service_integration.backfill_secret,},
|
|
150
|
+
logger: self.logger,
|
|
151
|
+
timeout: Webhookdb::Shopify.http_timeout,
|
|
152
|
+
)
|
|
153
|
+
data = response.parsed_response
|
|
154
|
+
next_link = nil
|
|
155
|
+
if response.headers.key?("link")
|
|
156
|
+
links = Webhookdb::Shopify.parse_link_header(response.headers["link"])
|
|
157
|
+
next_link = links[:next] if links.key?(:next)
|
|
158
|
+
end
|
|
159
|
+
return data[self._mixin_backfill_hashkey], next_link
|
|
160
|
+
end
|
|
161
|
+
end
|