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,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/replicator/transistor_v1_mixin"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::TransistorShowV1 < Webhookdb::Replicator::Base
|
|
6
|
+
include Appydays::Loggable
|
|
7
|
+
include Webhookdb::Replicator::TransistorV1Mixin
|
|
8
|
+
|
|
9
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
10
|
+
def self.descriptor
|
|
11
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
12
|
+
name: "transistor_show_v1",
|
|
13
|
+
ctor: ->(sint) { Webhookdb::Replicator::TransistorShowV1.new(sint) },
|
|
14
|
+
feature_roles: [],
|
|
15
|
+
resource_name_singular: "Transistor Show",
|
|
16
|
+
supports_backfill: true,
|
|
17
|
+
api_docs_url: "https://developers.transistor.fm/#Show",
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _denormalized_columns
|
|
22
|
+
return [
|
|
23
|
+
Webhookdb::Replicator::Column.new(:author, TEXT, data_key: ["attributes", "author"]),
|
|
24
|
+
Webhookdb::Replicator::Column.new(
|
|
25
|
+
:created_at, TIMESTAMP,
|
|
26
|
+
index: true,
|
|
27
|
+
data_key: ["attributes", "created_at"],
|
|
28
|
+
),
|
|
29
|
+
Webhookdb::Replicator::Column.new(:description, TEXT, data_key: ["attributes", "description"]),
|
|
30
|
+
Webhookdb::Replicator::Column.new(:title, TEXT, data_key: ["attributes", "title"]),
|
|
31
|
+
Webhookdb::Replicator::Column.new(
|
|
32
|
+
:updated_at,
|
|
33
|
+
TIMESTAMP,
|
|
34
|
+
index: true,
|
|
35
|
+
data_key: ["attributes", "updated_at"],
|
|
36
|
+
),
|
|
37
|
+
Webhookdb::Replicator::Column.new(:website, TEXT, data_key: ["attributes", "website"]),
|
|
38
|
+
]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def _fetch_backfill_page(pagination_token, **_kwargs)
|
|
42
|
+
pagination_token ||= [false, 1]
|
|
43
|
+
iterating_over_private, page = pagination_token
|
|
44
|
+
|
|
45
|
+
url = "https://api.transistor.fm/v1/shows"
|
|
46
|
+
|
|
47
|
+
response = Webhookdb::Http.get(
|
|
48
|
+
url,
|
|
49
|
+
headers: {"x-api-key" => self.service_integration.backfill_key},
|
|
50
|
+
body: {pagination: {page:}, private: iterating_over_private},
|
|
51
|
+
logger: self.logger,
|
|
52
|
+
timeout: Webhookdb::Transistor.http_timeout,
|
|
53
|
+
)
|
|
54
|
+
data = response.parsed_response
|
|
55
|
+
current_page = data["meta"]["currentPage"]
|
|
56
|
+
total_pages = data["meta"]["totalPages"]
|
|
57
|
+
shows = data["data"]
|
|
58
|
+
|
|
59
|
+
if current_page < total_pages
|
|
60
|
+
# If we still have pages on this list, go to the next one
|
|
61
|
+
return shows, [iterating_over_private, current_page + 1]
|
|
62
|
+
end
|
|
63
|
+
# If we are done with the public list, we can now iterate over private shows
|
|
64
|
+
return shows, [true, 1] unless iterating_over_private
|
|
65
|
+
# Otherwise we are on the last page of our private list
|
|
66
|
+
return shows, nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::Replicator::TransistorV1Mixin
|
|
4
|
+
include Webhookdb::DBAdapter::ColumnTypes
|
|
5
|
+
|
|
6
|
+
def _webhook_response(_request)
|
|
7
|
+
# As of 9/15/21 there is no way to verify authenticity of these webhooks
|
|
8
|
+
return Webhookdb::WebhookResponse.ok
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def _remote_key_column
|
|
12
|
+
return Webhookdb::Replicator::Column.new(:transistor_id, TEXT, data_key: "id")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def _timestamp_column_name
|
|
16
|
+
return :updated_at
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def _resource_and_event(request)
|
|
20
|
+
body = request.body
|
|
21
|
+
return body["data"], body if body.key?("data")
|
|
22
|
+
return body, nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def _update_where_expr
|
|
26
|
+
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def calculate_backfill_state_machine
|
|
30
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
31
|
+
if self.service_integration.backfill_key.blank?
|
|
32
|
+
step.output = %(Great! We've created your #{self.resource_name_plural} integration.
|
|
33
|
+
|
|
34
|
+
Transistor does not support #{self.resource_name_singular} webhooks, so to fill your database,
|
|
35
|
+
we need to use the API to make requests, which requires your API Key.
|
|
36
|
+
|
|
37
|
+
From your Transistor dashboard, go to the "Your Account" page,
|
|
38
|
+
at https://dashboard.transistor.fm/account
|
|
39
|
+
On the left side of the bottom of the page you should be able to see your API key.
|
|
40
|
+
|
|
41
|
+
Copy that API key.
|
|
42
|
+
)
|
|
43
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
47
|
+
self.service_integration.replicator.clear_backfill_information
|
|
48
|
+
step.output = result.message
|
|
49
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
|
|
53
|
+
#{self._query_help_output}
|
|
54
|
+
)
|
|
55
|
+
return step.completed
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def _verify_backfill_401_err_msg
|
|
59
|
+
return "It looks like that API Key is invalid. Please reenter the API Key you just created:"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def _verify_backfill_err_msg
|
|
63
|
+
return "An error occurred. Please reenter the API Key you just created:"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/twilio"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Replicator::TwilioSmsV1 < 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: "twilio_sms_v1",
|
|
12
|
+
ctor: ->(sint) { Webhookdb::Replicator::TwilioSmsV1.new(sint) },
|
|
13
|
+
feature_roles: [],
|
|
14
|
+
resource_name_singular: "Twilio SMS Message",
|
|
15
|
+
supports_backfill: true,
|
|
16
|
+
api_docs_url: "https://www.twilio.com/docs/usage/api#send-an-sms-with-twilios-api",
|
|
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.backfill_key.present?
|
|
49
|
+
step.needs_input = true
|
|
50
|
+
step.output = %(Great! We've created your Twilio SMS integration.
|
|
51
|
+
|
|
52
|
+
Rather than using your Twilio Webhooks (of which each number can have only one),
|
|
53
|
+
we poll Twilio for changes, and will also backfill historical SMS.
|
|
54
|
+
|
|
55
|
+
To do this, we need your Account SID and Auth Token.
|
|
56
|
+
Both of these values should be visible from the homepage of your Twilio admin Dashboard.
|
|
57
|
+
)
|
|
58
|
+
return step.secret_prompt("Account SID").backfill_key(self.service_integration)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless self.service_integration.backfill_secret.present?
|
|
62
|
+
return step.secret_prompt("Auth Token").backfill_secret(self.service_integration)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless (result = self.verify_backfill_credentials).verified
|
|
66
|
+
self.service_integration.replicator.clear_backfill_information
|
|
67
|
+
step.output = result.message
|
|
68
|
+
return step.secret_prompt("API Key").backfill_key(self.service_integration)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
step.output = %(We are going to start replicating your Twilio SMS information, and will keep it updated.
|
|
72
|
+
#{self._query_help_output}
|
|
73
|
+
)
|
|
74
|
+
return step.completed
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def _verify_backfill_401_err_msg
|
|
78
|
+
return "It looks like that API Key is invalid. Please reenter the API Key you just created:"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _verify_backfill_err_msg
|
|
82
|
+
return "An error occurred. Please reenter the API Key you just created:"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _remote_key_column
|
|
86
|
+
return Webhookdb::Replicator::Column.new(:twilio_id, TEXT, data_key: "sid")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _denormalized_columns
|
|
90
|
+
return [
|
|
91
|
+
Webhookdb::Replicator::Column.new(
|
|
92
|
+
:date_created,
|
|
93
|
+
TIMESTAMP,
|
|
94
|
+
index: true,
|
|
95
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
96
|
+
),
|
|
97
|
+
Webhookdb::Replicator::Column.new(
|
|
98
|
+
:date_sent,
|
|
99
|
+
TIMESTAMP,
|
|
100
|
+
index: true,
|
|
101
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
102
|
+
),
|
|
103
|
+
Webhookdb::Replicator::Column.new(
|
|
104
|
+
:date_updated,
|
|
105
|
+
TIMESTAMP,
|
|
106
|
+
index: true,
|
|
107
|
+
converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
|
|
108
|
+
),
|
|
109
|
+
Webhookdb::Replicator::Column.new(:direction, TEXT),
|
|
110
|
+
Webhookdb::Replicator::Column.new(:from, TEXT, index: true),
|
|
111
|
+
Webhookdb::Replicator::Column.new(:status, TEXT),
|
|
112
|
+
Webhookdb::Replicator::Column.new(:to, TEXT, index: true),
|
|
113
|
+
]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def _timestamp_column_name
|
|
117
|
+
return :date_updated
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def _resource_and_event(request)
|
|
121
|
+
return request.body, nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def _update_where_expr
|
|
125
|
+
return self.qualified_table_sequel_identifier[:date_updated] < Sequel[:excluded][:date_updated]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def _fetch_backfill_page(pagination_token, last_backfilled:)
|
|
129
|
+
url = "https://api.twilio.com"
|
|
130
|
+
if pagination_token.blank?
|
|
131
|
+
date_send_max = Date.tomorrow
|
|
132
|
+
url += "/2010-04-01/Accounts/#{self.service_integration.backfill_key}/Messages.json" \
|
|
133
|
+
"?PageSize=100&DateSend%3C=#{date_send_max}"
|
|
134
|
+
else
|
|
135
|
+
url += pagination_token
|
|
136
|
+
end
|
|
137
|
+
response = Webhookdb::Http.get(
|
|
138
|
+
url,
|
|
139
|
+
basic_auth: {username: self.service_integration.backfill_key,
|
|
140
|
+
password: self.service_integration.backfill_secret,},
|
|
141
|
+
logger: self.logger,
|
|
142
|
+
timeout: Webhookdb::Twilio.http_timeout,
|
|
143
|
+
)
|
|
144
|
+
data = response.parsed_response
|
|
145
|
+
messages = data["messages"]
|
|
146
|
+
|
|
147
|
+
if last_backfilled.present?
|
|
148
|
+
earliest_data_created = messages.empty? ? Time.at(0) : messages[-1].fetch("date_created")
|
|
149
|
+
paged_to_already_seen_records = earliest_data_created < last_backfilled
|
|
150
|
+
|
|
151
|
+
return messages, nil if paged_to_already_seen_records
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
return messages, data["next_page_uri"]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::Replicator::WebhookdbCustomerV1 < Webhookdb::Replicator::Base
|
|
4
|
+
include Appydays::Loggable
|
|
5
|
+
|
|
6
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
7
|
+
def self.descriptor
|
|
8
|
+
return Webhookdb::Replicator::Descriptor.new(
|
|
9
|
+
name: "webhookdb_customer_v1",
|
|
10
|
+
ctor: ->(sint) { Webhookdb::Replicator::WebhookdbCustomerV1.new(sint) },
|
|
11
|
+
feature_roles: ["internal"],
|
|
12
|
+
resource_name_singular: "WebookDB Customer",
|
|
13
|
+
supports_webhooks: true,
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def _webhook_response(request)
|
|
18
|
+
sek = request.env["HTTP_WHDB_SECRET"]
|
|
19
|
+
return Webhookdb::WebhookResponse.ok if sek == self.service_integration.webhook_secret
|
|
20
|
+
return Webhookdb::WebhookResponse.error("Whdb-Secret header is missing") if sek.nil?
|
|
21
|
+
return Webhookdb::WebhookResponse.error("Whdb-Secret value does not match configured secret")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _timestamp_column_name
|
|
25
|
+
return :updated_at
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def calculate_webhook_state_machine
|
|
29
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
30
|
+
self.service_integration.update(webhook_secret: Webhookdb::Id.rand_enc(16)) if
|
|
31
|
+
self.service_integration.webhook_secret.blank?
|
|
32
|
+
step.output = %(WebhookDB is now listening for changes to #{self.resource_name_plural}
|
|
33
|
+
and will replicate them into the table for this service integration.
|
|
34
|
+
|
|
35
|
+
Whenever a #{self.resource_name_singular} changes, a request will be sent to:
|
|
36
|
+
|
|
37
|
+
#{self._webhook_endpoint}
|
|
38
|
+
|
|
39
|
+
With the header:
|
|
40
|
+
|
|
41
|
+
Whdb-Secret: #{self.service_integration.webhook_secret}
|
|
42
|
+
|
|
43
|
+
Which will be received by this running instance, so there's nothing else you have to do.
|
|
44
|
+
|
|
45
|
+
#{self._query_help_output}
|
|
46
|
+
)
|
|
47
|
+
return step.completed
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _remote_key_column
|
|
51
|
+
return Webhookdb::Replicator::Column.new(:webhookdb_id, TEXT, data_key: "id")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def _denormalized_columns
|
|
55
|
+
return [
|
|
56
|
+
Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
|
|
57
|
+
Webhookdb::Replicator::Column.new(:email, TEXT, index: true),
|
|
58
|
+
Webhookdb::Replicator::Column.new(
|
|
59
|
+
:updated_at,
|
|
60
|
+
TIMESTAMP,
|
|
61
|
+
index: true,
|
|
62
|
+
defaulter: Webhookdb::Replicator::Column.defaulter_from_resource_field(:created_at),
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def _resource_and_event(request)
|
|
68
|
+
return request.body, nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def _update_where_expr
|
|
72
|
+
return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "webhookdb/typed_struct"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::Replicator
|
|
7
|
+
include Appydays::Configurable
|
|
8
|
+
extend Webhookdb::MethodUtilities
|
|
9
|
+
|
|
10
|
+
configurable(:replicator) do
|
|
11
|
+
setting :always_process_synchronously, false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
PLUGIN_DIRNAME = "replicator_ext"
|
|
15
|
+
PLUGIN_DIR = Pathname(__FILE__).dirname + PLUGIN_DIRNAME
|
|
16
|
+
|
|
17
|
+
# Raised when there is no service registered for a name.
|
|
18
|
+
class Invalid < StandardError; end
|
|
19
|
+
|
|
20
|
+
# Raised when credentials to interact with a service are not set up.
|
|
21
|
+
# Usually this is due to a missing dependency.
|
|
22
|
+
class CredentialsMissing < StandardError; end
|
|
23
|
+
|
|
24
|
+
# Statically describe a replicator.
|
|
25
|
+
class Descriptor < Webhookdb::TypedStruct
|
|
26
|
+
# @!attribute name
|
|
27
|
+
# Name of the replicator, like 'stripe_charge_v1'.
|
|
28
|
+
# Appears externally in many places, so must be meaningful.
|
|
29
|
+
# @return [String]
|
|
30
|
+
attr_reader :name
|
|
31
|
+
|
|
32
|
+
# @!attribute ctor
|
|
33
|
+
# Method invoked with the +Webhookdb::ServiceIntegration+, and should return a new instance
|
|
34
|
+
# of the integration. Can also be an object that responds to `new`.
|
|
35
|
+
# @return [Proc]
|
|
36
|
+
attr_reader :ctor
|
|
37
|
+
|
|
38
|
+
# @!attribute feature_roles
|
|
39
|
+
# Used for feature flagging functionality.
|
|
40
|
+
# Usually will be [], but other possible values are:
|
|
41
|
+
# -'internal' e.g. our fake integration
|
|
42
|
+
# -'unreleased' for works in progress
|
|
43
|
+
# -'beta' if we don't want most people to have access
|
|
44
|
+
# @return [Array<String>]
|
|
45
|
+
attr_reader :feature_roles
|
|
46
|
+
|
|
47
|
+
# @!attribute resource_name_singular
|
|
48
|
+
# Name of the resource, like "Acme Sprocket"
|
|
49
|
+
# @return [String]
|
|
50
|
+
attr_reader :resource_name_singular
|
|
51
|
+
|
|
52
|
+
# @!attribute resource_name_plural
|
|
53
|
+
# Defaults to resource_name_singular+s.
|
|
54
|
+
# @return [String]
|
|
55
|
+
attr_reader :resource_name_plural
|
|
56
|
+
|
|
57
|
+
# @!attribute dependency_descriptor
|
|
58
|
+
# The descriptor for the service this one depends on (the parent).
|
|
59
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
60
|
+
attr_reader :dependency_descriptor
|
|
61
|
+
|
|
62
|
+
# True if this integration supports webhooks (real-time or user-built webhook payloads).
|
|
63
|
+
attr_reader :supports_webhooks
|
|
64
|
+
# True if this integration supports user-driven backfilling,
|
|
65
|
+
# usually by paginating all resources.
|
|
66
|
+
attr_reader :supports_backfill
|
|
67
|
+
|
|
68
|
+
# If this integration has specific documentation,
|
|
69
|
+
# link its url here. It is used to build custom error messages,
|
|
70
|
+
# for example in case 'backfill' is called but not supported.
|
|
71
|
+
attr_reader :documentation_url
|
|
72
|
+
|
|
73
|
+
# Markdown description of this replicator.
|
|
74
|
+
attr_reader :description
|
|
75
|
+
|
|
76
|
+
# URL pointing to the provider (not-WebhookDB) docs for this integration.
|
|
77
|
+
attr_reader :api_docs_url
|
|
78
|
+
|
|
79
|
+
# Is this an enterprise-only replicator?
|
|
80
|
+
attr_reader :enterprise
|
|
81
|
+
|
|
82
|
+
def initialize(
|
|
83
|
+
name:,
|
|
84
|
+
ctor:,
|
|
85
|
+
resource_name_singular:,
|
|
86
|
+
feature_roles:,
|
|
87
|
+
supports_webhooks: false,
|
|
88
|
+
supports_backfill: false,
|
|
89
|
+
resource_name_plural: nil,
|
|
90
|
+
dependency_descriptor: nil,
|
|
91
|
+
api_docs_url: "",
|
|
92
|
+
description: nil,
|
|
93
|
+
enterprise: false,
|
|
94
|
+
documentation_url: nil
|
|
95
|
+
)
|
|
96
|
+
raise ArgumentError, "must support one or both of webhooks and backfill" unless
|
|
97
|
+
supports_webhooks || supports_backfill
|
|
98
|
+
super(
|
|
99
|
+
name:,
|
|
100
|
+
resource_name_singular:,
|
|
101
|
+
feature_roles:,
|
|
102
|
+
supports_webhooks:,
|
|
103
|
+
supports_backfill:,
|
|
104
|
+
dependency_descriptor:,
|
|
105
|
+
documentation_url:,
|
|
106
|
+
api_docs_url:,
|
|
107
|
+
enterprise:
|
|
108
|
+
)
|
|
109
|
+
@ctor = ctor.is_a?(Class) ? ctor.method(:new) : ctor
|
|
110
|
+
@resource_name_plural = resource_name_plural || "#{self.resource_name_singular}s"
|
|
111
|
+
@description = description || "Replicate #{self.resource_name_plural} into your database."
|
|
112
|
+
self.feature_roles
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def inspect
|
|
116
|
+
return "#{self.class.name}(name: #{self.name})"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def ==(other)
|
|
120
|
+
return self.class == other.class &&
|
|
121
|
+
self.name == other.name &&
|
|
122
|
+
self.resource_name_singular == other.resource_name_singular
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
alias supports_webhooks? supports_webhooks
|
|
126
|
+
alias supports_backfill? supports_backfill
|
|
127
|
+
alias enterprise? enterprise
|
|
128
|
+
def webhooks_only? = self.supports_webhooks? && !self.supports_backfill?
|
|
129
|
+
def backfill_only? = !self.supports_webhooks? && self.supports_backfill?
|
|
130
|
+
def supports_webhooks_and_backfill? = self.supports_webhooks? && self.supports_backfill?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class IndexSpec < Webhookdb::TypedStruct
|
|
134
|
+
attr_reader :columns, :where
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class << self
|
|
138
|
+
# @return [Hash{String => Webhookdb::Replicator::Descriptor}]
|
|
139
|
+
def registry
|
|
140
|
+
return @registry ||= {}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def register(cls)
|
|
144
|
+
desc = cls.descriptor
|
|
145
|
+
raise TypeError, "descriptor must be a Descriptor, got #{desc.class.name}" unless desc.is_a?(Descriptor)
|
|
146
|
+
self.registry[desc.name] = desc
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Return a new replicator for the given integration.
|
|
150
|
+
#
|
|
151
|
+
# @param service_integration [Webhookdb::ServiceIntegration]
|
|
152
|
+
# @return [Webhookdb::Replicator::Base]
|
|
153
|
+
def create(service_integration)
|
|
154
|
+
name = service_integration.service_name
|
|
155
|
+
descr = self.registered!(name)
|
|
156
|
+
return descr.ctor.call(service_integration)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Returns the service with the given name, or +nil+ if none is registered.
|
|
160
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
161
|
+
def registered(name)
|
|
162
|
+
return @registry[name]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @raise [Webhookdb::Replicator::Invalid] When the name is invalid.
|
|
166
|
+
# @return [Webhookdb::Replicator::Descriptor]
|
|
167
|
+
def registered!(name)
|
|
168
|
+
raise ArgumentError, "name cannot be blank" if name.blank?
|
|
169
|
+
r = self.registered(name)
|
|
170
|
+
return r if r
|
|
171
|
+
raise Invalid, name
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def load_replicators
|
|
175
|
+
existing_descendants = Webhookdb::Replicator::Base.descendants
|
|
176
|
+
["replicator", PLUGIN_DIRNAME].each do |d|
|
|
177
|
+
Gem.find_files(File.join("webhookdb/#{d}/*.rb")).each do |path|
|
|
178
|
+
next if path.include?("/spec/")
|
|
179
|
+
require path
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
new_descendants = Webhookdb::Replicator::Base.descendants
|
|
183
|
+
newly_registered = new_descendants - existing_descendants
|
|
184
|
+
newly_registered.each { |cls| self.register(cls) }
|
|
185
|
+
return newly_registered
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def _require_files(dir)
|
|
189
|
+
splitter = "webhookdb/" + dir.to_s.rpartition("/").last
|
|
190
|
+
dir.glob("*.rb").each do |path|
|
|
191
|
+
base = path.basename.to_s[..-4]
|
|
192
|
+
require("#{splitter}/#{base}")
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @param sint [Webhookdb::ServiceIntegration]
|
|
197
|
+
# @return [Webhookdb::ServiceIntegration,nil]
|
|
198
|
+
def find_root(sint)
|
|
199
|
+
max_depth = 15
|
|
200
|
+
parent = sint.depends_on
|
|
201
|
+
return sint if parent.nil?
|
|
202
|
+
max_depth.times do
|
|
203
|
+
return parent if parent.depends_on.nil?
|
|
204
|
+
parent = parent.depends_on
|
|
205
|
+
end
|
|
206
|
+
return nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @param sint [Webhookdb::ServiceIntegration]
|
|
210
|
+
# @return [Webhookdb::ServiceIntegration]
|
|
211
|
+
def find_at_root!(sint, service_name:)
|
|
212
|
+
root = self.find_root(sint)
|
|
213
|
+
bad_auth = root&.service_name != service_name
|
|
214
|
+
raise self::CredentialsMissing, "Could not find root integration for #{sint.inspect}" if bad_auth
|
|
215
|
+
return root
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
require "webhookdb/replicator/state_machine_step"
|
|
220
|
+
require "webhookdb/replicator/column"
|
|
221
|
+
require "webhookdb/replicator/base"
|
|
222
|
+
require "webhookdb/replicator/docgen"
|
|
223
|
+
load_replicators
|
|
224
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Role < Webhookdb::Postgres::Model(:roles)
|
|
6
|
+
# n.b. Because of the uniqueness constraint on "name", there is only one "admin" role. Its meaning
|
|
7
|
+
# depends on the context: if the customer has this role, they are an admin; if the org membership has
|
|
8
|
+
# this role, the customer is an org admin.
|
|
9
|
+
def self.admin_role
|
|
10
|
+
return Webhookdb.cached_get("role_admin") do
|
|
11
|
+
self.find_or_create_or_find(name: "admin")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.non_admin_role
|
|
16
|
+
return Webhookdb.cached_get("role_member") do
|
|
17
|
+
self.find_or_create_or_find(name: "member")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# used to indicate user status within the org, e.g. whether user is an org admin & can create services
|
|
22
|
+
one_to_many :organization_memberships, class: "Webhookdb::OrganizationMembership"
|
|
23
|
+
|
|
24
|
+
# used to indicate user status within the app itself, i.e. whether user is an app admin
|
|
25
|
+
many_to_many :customers,
|
|
26
|
+
class: "Webhookdb::Customer",
|
|
27
|
+
join_table: :roles_customers
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Table: roles
|
|
31
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
32
|
+
# Columns:
|
|
33
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
34
|
+
# name | text | NOT NULL
|
|
35
|
+
# Indexes:
|
|
36
|
+
# roles_pkey | PRIMARY KEY btree (id)
|
|
37
|
+
# roles_name_key | UNIQUE btree (name)
|
|
38
|
+
# Referenced By:
|
|
39
|
+
# feature_roles_organizations | feature_roles_organizations_role_id_fkey | (role_id) REFERENCES roles(id)
|
|
40
|
+
# organization_memberships | organization_memberships_membership_role_id_fkey | (membership_role_id) REFERENCES roles(id)
|
|
41
|
+
# roles_customers | roles_customers_role_id_fkey | (role_id) REFERENCES roles(id)
|
|
42
|
+
# ---------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry-ruby"
|
|
4
|
+
require "appydays/configurable"
|
|
5
|
+
require "appydays/loggable"
|
|
6
|
+
|
|
7
|
+
require "webhookdb"
|
|
8
|
+
|
|
9
|
+
module Webhookdb::Sentry
|
|
10
|
+
include Appydays::Configurable
|
|
11
|
+
include Appydays::Loggable
|
|
12
|
+
|
|
13
|
+
configurable(:sentry) do
|
|
14
|
+
setting :dsn, ""
|
|
15
|
+
|
|
16
|
+
# Apply the current configuration to Sentry.
|
|
17
|
+
# See https://docs.sentry.io/clients/ruby/config/ for more info.
|
|
18
|
+
after_configured do
|
|
19
|
+
if self.dsn
|
|
20
|
+
# See https://github.com/getsentry/sentry-ruby/issues/1756
|
|
21
|
+
require "sentry-sidekiq"
|
|
22
|
+
Sentry.init do |config|
|
|
23
|
+
config.dsn = dsn
|
|
24
|
+
config.logger = self.logger
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
Sentry.instance_variable_set(:@main_hub, nil)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.enabled?
|
|
33
|
+
return self.dsn.present?
|
|
34
|
+
end
|
|
35
|
+
end
|