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,491 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sequel/advisory_lock"
|
|
4
|
+
require "sequel/database"
|
|
5
|
+
|
|
6
|
+
# Support exporting WebhookDB data into external services,
|
|
7
|
+
# such as another Postgres instance or data warehouse (Snowflake, etc).
|
|
8
|
+
#
|
|
9
|
+
# At a high level, the way sync targets work are:
|
|
10
|
+
# - User uses the CLI to register a sync target for a specific integration
|
|
11
|
+
# using a database connection string for a supported db (ie, postgres://).
|
|
12
|
+
# - They include a period (how often it is synced), and an optional schema and table
|
|
13
|
+
# (if not used, we'll use the default schema, and the service integration table name).
|
|
14
|
+
# - Every minute or so, we look for sync targets that are "due" and enqueue a sync for them.
|
|
15
|
+
# Customers can enqueue their own sync request; but it cannot run more than the
|
|
16
|
+
# minimum allowed sync time.
|
|
17
|
+
#
|
|
18
|
+
# For the sync logic itself, see +run_sync+.
|
|
19
|
+
#
|
|
20
|
+
class Webhookdb::SyncTarget < Webhookdb::Postgres::Model(:sync_targets)
|
|
21
|
+
include Appydays::Configurable
|
|
22
|
+
include Webhookdb::Dbutil
|
|
23
|
+
|
|
24
|
+
class Deleted < StandardError; end
|
|
25
|
+
class InvalidConnection < StandardError; end
|
|
26
|
+
class SyncInProgress < StandardError; end
|
|
27
|
+
|
|
28
|
+
# Advisory locks for sync targets use this as the first int, and the id as the second.
|
|
29
|
+
ADVISORY_LOCK_KEYSPACE = 2_000_000_000
|
|
30
|
+
|
|
31
|
+
HTTP_VERIFY_TIMEOUT = 3
|
|
32
|
+
DB_VERIFY_TIMEOUT = 2000
|
|
33
|
+
DB_VERIFY_STATEMENT = "SELECT 1"
|
|
34
|
+
RAND = Random.new
|
|
35
|
+
|
|
36
|
+
configurable(:sync_target) do
|
|
37
|
+
# Allow installs to set this much lower if they want a faster sync.
|
|
38
|
+
# On production we use 1 minute as a default since it's faster than the replication delay
|
|
39
|
+
# of similar services but not fast enough to discourage self-hosting (which can be immediate).
|
|
40
|
+
# We have 10 minutes here for test compatibility (API endpoints read and store this when they are built).
|
|
41
|
+
# Can be overridden per-organization.
|
|
42
|
+
setting :default_min_period_seconds, 10.minutes.to_i
|
|
43
|
+
setting :max_period_seconds, 24.hours.to_i
|
|
44
|
+
# How many items sent in each POST for http sync targets.
|
|
45
|
+
setting :default_page_size, 200
|
|
46
|
+
# Sync targets without an explicit schema set
|
|
47
|
+
# will add tables into this schema. We use public by default
|
|
48
|
+
# since it's convenient, but for tests, it could cause conflicts
|
|
49
|
+
# so something else is set instead.
|
|
50
|
+
setting :default_schema, "public"
|
|
51
|
+
# If we want to sync to a localhost url for development purposes,
|
|
52
|
+
# we must allow sync targets to use http urls. This should only
|
|
53
|
+
# be used internally, and never in production.
|
|
54
|
+
setting :allow_http, false
|
|
55
|
+
|
|
56
|
+
after_configured do
|
|
57
|
+
if Webhookdb::RACK_ENV == "test"
|
|
58
|
+
safename = ENV["USER"].gsub(/[^A-Za-z]/, "")
|
|
59
|
+
self.default_schema = "synctest_#{safename}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.valid_period(beginval)
|
|
65
|
+
return beginval..self.max_period_seconds
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.valid_period_for(org)
|
|
69
|
+
return self.valid_period(org.minimum_sync_seconds)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.default_valid_period
|
|
73
|
+
return self.valid_period(Webhookdb::SyncTarget.default_min_period_seconds)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
plugin :timestamps
|
|
77
|
+
plugin :column_encryption do |enc|
|
|
78
|
+
enc.column :connection_url
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Eventually we will allow full sync of an org's data,
|
|
82
|
+
# but for now let's link to just a service integration
|
|
83
|
+
many_to_one :service_integration, class: Webhookdb::ServiceIntegration
|
|
84
|
+
many_to_one :created_by, class: Webhookdb::Customer
|
|
85
|
+
|
|
86
|
+
dataset_module do
|
|
87
|
+
def due_for_sync(as_of:)
|
|
88
|
+
never_synced = Sequel[last_synced_at: nil]
|
|
89
|
+
next_due_at = Sequel[:last_synced_at] + (Sequel.lit("INTERVAL '1 second'") * Sequel[:period_seconds])
|
|
90
|
+
due_before_now = next_due_at <= as_of
|
|
91
|
+
return self.where(never_synced | due_before_now)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def http?
|
|
96
|
+
url = URI(self.connection_url)
|
|
97
|
+
return true if ["http", "https"].include?(url.scheme)
|
|
98
|
+
return false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def db?
|
|
102
|
+
return !self.http?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.validate_db_url(s)
|
|
106
|
+
begin
|
|
107
|
+
url = URI(s)
|
|
108
|
+
rescue URI::InvalidURIError
|
|
109
|
+
return "That's not a valid URL."
|
|
110
|
+
end
|
|
111
|
+
protocols = ["postgres", "snowflake"]
|
|
112
|
+
unless protocols.include?(url.scheme)
|
|
113
|
+
protostr = protocols.join(", ")
|
|
114
|
+
# rubocop:disable Layout/LineLength
|
|
115
|
+
msg = "The '#{url.scheme}' protocol is not supported for database sync targets. Supported protocols are: #{protostr}."
|
|
116
|
+
# rubocop:enable Layout/LineLength
|
|
117
|
+
return msg
|
|
118
|
+
end
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.validate_http_url(s)
|
|
123
|
+
begin
|
|
124
|
+
url = URI(s)
|
|
125
|
+
rescue URI::InvalidURIError
|
|
126
|
+
return "That's not a valid URL."
|
|
127
|
+
end
|
|
128
|
+
case url.scheme
|
|
129
|
+
when "https"
|
|
130
|
+
return nil if url.user.present? || url.password.present?
|
|
131
|
+
url.user = "user"
|
|
132
|
+
url.password = "pass"
|
|
133
|
+
return "https urls must include a Basic Auth username and/or password, like '#{url}'"
|
|
134
|
+
when "http"
|
|
135
|
+
# http does not require a username/pass since it's only for internal use.
|
|
136
|
+
return Webhookdb::SyncTarget.allow_http ? nil : "Url must be https, not http."
|
|
137
|
+
else
|
|
138
|
+
return "Must be an https url."
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.verify_db_connection(url)
|
|
143
|
+
adapter = Webhookdb::DBAdapter.adapter(url)
|
|
144
|
+
begin
|
|
145
|
+
adapter.verify_connection(url, timeout: DB_VERIFY_TIMEOUT, statement: DB_VERIFY_STATEMENT)
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
# noinspection RailsParamDefResolve
|
|
148
|
+
msg = e.try(:wrapped_exception).try(:to_s) || e.to_s
|
|
149
|
+
raise InvalidConnection, "Could not SELECT 1: #{msg.strip}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.verify_http_connection(url)
|
|
154
|
+
cleanurl, authparams = Webhookdb::Http.extract_url_auth(url)
|
|
155
|
+
body = {
|
|
156
|
+
rows: [],
|
|
157
|
+
integration_id: "svi_test",
|
|
158
|
+
integration_service: "httpsync_test",
|
|
159
|
+
table: "test",
|
|
160
|
+
}
|
|
161
|
+
begin
|
|
162
|
+
Webhookdb::Http.post(
|
|
163
|
+
cleanurl,
|
|
164
|
+
body,
|
|
165
|
+
logger: self.logger,
|
|
166
|
+
basic_auth: authparams,
|
|
167
|
+
timeout: HTTP_VERIFY_TIMEOUT,
|
|
168
|
+
follow_redirects: true,
|
|
169
|
+
)
|
|
170
|
+
rescue Timeout::Error => e
|
|
171
|
+
raise InvalidConnection, "POST to #{cleanurl} timed out: #{e.message}"
|
|
172
|
+
rescue Webhookdb::Http::Error => e
|
|
173
|
+
raise InvalidConnection, "POST to #{cleanurl} failed: #{e.message}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def next_scheduled_sync(now:)
|
|
178
|
+
return self.next_sync(self.period_seconds, now)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def next_possible_sync(now:)
|
|
182
|
+
return self.next_sync(self.organization.minimum_sync_seconds, now)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
protected def next_sync(period, now)
|
|
186
|
+
return now if self.last_synced_at.nil?
|
|
187
|
+
return [now, self.last_synced_at + period].max
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Return the jitter used for enqueing the next sync of the job.
|
|
191
|
+
# It should never be more than 20 seconds,
|
|
192
|
+
# nor should it be more than 1/4 of the total period,
|
|
193
|
+
# since it needs to run at a reasonably predictable time.
|
|
194
|
+
# Jitter is always >= 1, since it is helpful to be able to assert it
|
|
195
|
+
# will always be in the future.
|
|
196
|
+
def jitter
|
|
197
|
+
max_jitter = [20, self.period_seconds / 4].min
|
|
198
|
+
max_jitter = [1, max_jitter].max
|
|
199
|
+
return RAND.rand(1..max_jitter)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Running a sync involves some work we always do (export, transform),
|
|
203
|
+
# and then work that varies per-adapter (load).
|
|
204
|
+
#
|
|
205
|
+
# First, we lock using an advisory lock to make sure we never sync the same sync target
|
|
206
|
+
# concurrently. It can cause correctness and performance issues.
|
|
207
|
+
# Raise a +SyncInProgress+ error if we're currently syncing.
|
|
208
|
+
#
|
|
209
|
+
# If the sync target is against an HTTP URL, see +_run_http_sync+.
|
|
210
|
+
#
|
|
211
|
+
# If the sync target is a database connection:
|
|
212
|
+
#
|
|
213
|
+
# - Ensure the sync target table exists and has the right schema.
|
|
214
|
+
# In general we do NOT create indices for the target table;
|
|
215
|
+
# since this table is for a client's data warehouse, we assume they will optimize it as needed.
|
|
216
|
+
# The only exception is the unique constraint for the remote key column.
|
|
217
|
+
# - Select rows created/updated since our last update in our 'source' database.
|
|
218
|
+
# - Write them to disk into a CSV file.
|
|
219
|
+
# - Pass this CSV file to the proper sync target adapter.
|
|
220
|
+
# - For example, the PG sync target will:
|
|
221
|
+
# - Create a temp table in the target database, using the schema from the sync target table.
|
|
222
|
+
# - Load the data into that temp table.
|
|
223
|
+
# - Insert rows into the target table temp table rows that do not appear in the target table.
|
|
224
|
+
# - Update rows in the target table temp table rows that already appear in the target table.
|
|
225
|
+
# - The snowflake sync target will:
|
|
226
|
+
# - PUT the CSV file into the stage for the table.
|
|
227
|
+
# - Otherwise the logic is the same as PG: create a temp table and COPY INTO from the CSV.
|
|
228
|
+
# - Purge the staged file.
|
|
229
|
+
#
|
|
230
|
+
# @param now [Time] The current time. Rows that were updated <= to 'now', and >= the 'last updated' timestamp,
|
|
231
|
+
# will be synced.
|
|
232
|
+
def run_sync(now:)
|
|
233
|
+
ran = false
|
|
234
|
+
# Take the advisory lock with a separate connection. This seems to be pretty important-
|
|
235
|
+
# it's possible that (for reasons not clear at this time) using the standard connection pool
|
|
236
|
+
# results in the lock being held since the session remains open for a while on the worker.
|
|
237
|
+
# Opening a separate connection ensures that, once this method exits, the lock will be released
|
|
238
|
+
# since the session will be ended.
|
|
239
|
+
Webhookdb::Dbutil.borrow_conn(Webhookdb::Postgres::Model.uri) do |db|
|
|
240
|
+
self.advisory_lock(db).with_lock? do
|
|
241
|
+
routine = if self.connection_url.start_with?("https://", "http://")
|
|
242
|
+
# Note that http links are not secure and should only be used for development purposes
|
|
243
|
+
HttpRoutine.new(now, self)
|
|
244
|
+
else
|
|
245
|
+
DatabaseRoutine.new(now, self)
|
|
246
|
+
end
|
|
247
|
+
routine.run
|
|
248
|
+
ran = true
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
raise SyncInProgress, "SyncTarget[#{self.id}] is already being synced" unless ran
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @return [Sequel::AdvisoryLock]
|
|
255
|
+
def advisory_lock(db)
|
|
256
|
+
return Sequel::AdvisoryLock.new(db, ADVISORY_LOCK_KEYSPACE, self.id)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def displaysafe_connection_url
|
|
260
|
+
return displaysafe_url(self.connection_url)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @return [String]
|
|
264
|
+
def associated_type
|
|
265
|
+
# Eventually we need to support orgs
|
|
266
|
+
return "service_integration"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# @return [String]
|
|
270
|
+
def associated_id
|
|
271
|
+
# Eventually we need to support orgs
|
|
272
|
+
return self.service_integration.opaque_id
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def associated_object_display
|
|
276
|
+
return "#{self.service_integration.opaque_id}/#{self.service_integration.table_name}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# @return [String]
|
|
280
|
+
def schema_and_table_string
|
|
281
|
+
schema_name = self.schema.present? ? self.schema : self.class.default_schema
|
|
282
|
+
table_name = self.table.present? ? self.table : self.service_integration.table_name
|
|
283
|
+
return "#{schema_name}.#{table_name}"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @return [Webhookdb::Organization]
|
|
287
|
+
def organization
|
|
288
|
+
return self.service_integration.organization
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def before_validation
|
|
292
|
+
self.page_size ||= Webhookdb::SyncTarget.default_page_size
|
|
293
|
+
super
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def before_create
|
|
297
|
+
self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("syt")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# @!attribute service_integration
|
|
301
|
+
# @return [Webhookdb::ServiceIntegration]
|
|
302
|
+
|
|
303
|
+
# @!attribute connection_url
|
|
304
|
+
# @return [String]
|
|
305
|
+
|
|
306
|
+
# @!attribute last_synced_at
|
|
307
|
+
# @return [Time]
|
|
308
|
+
|
|
309
|
+
class Routine
|
|
310
|
+
attr_reader :now, :sync_target, :replicator, :timestamp_expr
|
|
311
|
+
|
|
312
|
+
def initialize(now, sync_target)
|
|
313
|
+
@now = now
|
|
314
|
+
@sync_target = sync_target
|
|
315
|
+
@last_synced_at = sync_target.last_synced_at
|
|
316
|
+
@replicator = sync_target.service_integration.replicator
|
|
317
|
+
@timestamp_expr = Sequel[@replicator.timestamp_column.name]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def run = raise NotImplementedError
|
|
321
|
+
|
|
322
|
+
# Get the dataset of rows that need to be synced.
|
|
323
|
+
# Note that there are a couple race conditions here.
|
|
324
|
+
# First, those in https://github.com/webhookdb/webhookdb/issues/571.
|
|
325
|
+
# There is also the condition that we could send the same row
|
|
326
|
+
# multiple times when the row timestamp is set to last_synced_at but
|
|
327
|
+
# it wasn't in the last sync; however that is likely not a big problem
|
|
328
|
+
# since clients need to handle updates in any case.
|
|
329
|
+
def dataset_to_sync
|
|
330
|
+
@replicator.readonly_dataset do |ds|
|
|
331
|
+
# Find rows updated before we started
|
|
332
|
+
tscond = (@timestamp_expr <= @now)
|
|
333
|
+
# Find rows updated after the last sync was run
|
|
334
|
+
@last_synced_at && (tscond &= (@timestamp_expr >= @last_synced_at))
|
|
335
|
+
ds = ds.where(tscond)
|
|
336
|
+
# We want to paginate from oldest to newest
|
|
337
|
+
ds = ds.order(@timestamp_expr)
|
|
338
|
+
yield(ds)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def record(last_synced_at)
|
|
343
|
+
self.sync_target.update(last_synced_at:)
|
|
344
|
+
rescue Sequel::NoExistingObject => e
|
|
345
|
+
raise Webhookdb::SyncTarget::Deleted, e
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
class HttpRoutine < Routine
|
|
350
|
+
def run
|
|
351
|
+
page_size = self.sync_target.page_size
|
|
352
|
+
self.dataset_to_sync do |ds|
|
|
353
|
+
chunk = []
|
|
354
|
+
ds.paged_each(rows_per_fetch: page_size) do |row|
|
|
355
|
+
chunk << row
|
|
356
|
+
self._flush_http_chunk(chunk) if chunk.size >= page_size
|
|
357
|
+
end
|
|
358
|
+
self._flush_http_chunk(chunk) unless chunk.empty?
|
|
359
|
+
# We should save 'now' as the timestamp, rather than the last updated row.
|
|
360
|
+
# This is important because other we'd keep trying to sync the last row synced.
|
|
361
|
+
self.record(self.now)
|
|
362
|
+
end
|
|
363
|
+
rescue Webhookdb::Http::Error, Errno::ECONNRESET, Net::ReadTimeout, Net::OpenTimeout, OpenSSL::SSL::SSLError => e
|
|
364
|
+
# This is handled well so no need to re-raise.
|
|
365
|
+
# We already committed the last page that was successful,
|
|
366
|
+
# so we can just stop syncing at this point to try again later.
|
|
367
|
+
self.sync_target.logger.warn("sync_target_http_error", error: e)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def _flush_http_chunk(chunk)
|
|
371
|
+
sint = self.sync_target.service_integration
|
|
372
|
+
body = {
|
|
373
|
+
rows: chunk,
|
|
374
|
+
integration_id: sint.opaque_id,
|
|
375
|
+
integration_service: sint.service_name,
|
|
376
|
+
table: sint.table_name,
|
|
377
|
+
sync_timestamp: self.now,
|
|
378
|
+
}
|
|
379
|
+
cleanurl, authparams = Webhookdb::Http.extract_url_auth(self.sync_target.connection_url)
|
|
380
|
+
Webhookdb::Http.post(
|
|
381
|
+
cleanurl,
|
|
382
|
+
body,
|
|
383
|
+
timeout: sint.organization.sync_target_timeout,
|
|
384
|
+
logger: self.sync_target.logger,
|
|
385
|
+
basic_auth: authparams,
|
|
386
|
+
)
|
|
387
|
+
latest_ts = chunk.last.fetch(self.replicator.timestamp_column.name)
|
|
388
|
+
# The client committed the sync page we sent. Record it in case of a future error,
|
|
389
|
+
# so we don't re-send the same page.
|
|
390
|
+
self.record(latest_ts)
|
|
391
|
+
chunk.clear
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# - Ensure the sync target table exists and has the right schema.
|
|
396
|
+
# In general we do NOT create indices for the target table;
|
|
397
|
+
# since this table is for a client's data warehouse, we assume they will optimize it as needed.
|
|
398
|
+
# The only exception is the unique constraint for the remote key column.
|
|
399
|
+
# - Select rows created/updated since our last update in our 'source' database.
|
|
400
|
+
# - Write them to disk into a CSV file.
|
|
401
|
+
# - Pass this CSV file to the proper sync target adapter.
|
|
402
|
+
# - For example, the PG sync target will:
|
|
403
|
+
# - Create a temp table in the target database, using the schema from the sync target table.
|
|
404
|
+
# - Load the data into that temp table.
|
|
405
|
+
# - Insert rows into the target table temp table rows that do not appear in the target table.
|
|
406
|
+
# - Update rows in the target table temp table rows that already appear in the target table.
|
|
407
|
+
# - The snowflake sync target will:
|
|
408
|
+
# - PUT the CSV file into the stage for the table.
|
|
409
|
+
# - Otherwise the logic is the same as PG: create a temp table and COPY INTO from the CSV.
|
|
410
|
+
# - Purge the staged file.
|
|
411
|
+
#
|
|
412
|
+
class DatabaseRoutine < Routine
|
|
413
|
+
def initialize(now, sync_target)
|
|
414
|
+
super
|
|
415
|
+
@connection_url = self.sync_target.connection_url
|
|
416
|
+
@adapter = Webhookdb::DBAdapter.adapter(@connection_url)
|
|
417
|
+
@adapter_connection = @adapter.connection(@connection_url)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def run
|
|
421
|
+
schema_name = @sync_target.schema.present? ? @sync_target.schema : @sync_target.class.default_schema
|
|
422
|
+
table_name = @sync_target.table.present? ? @sync_target.table : @sync_target.service_integration.table_name
|
|
423
|
+
adapter = @adapter
|
|
424
|
+
schema = Webhookdb::DBAdapter::Schema.new(name: schema_name.to_sym)
|
|
425
|
+
table = Webhookdb::DBAdapter::Table.new(name: table_name.to_sym, schema:)
|
|
426
|
+
|
|
427
|
+
schema_lines = []
|
|
428
|
+
schema_lines << adapter.create_schema_sql(table.schema, if_not_exists: true)
|
|
429
|
+
schema_lines << adapter.create_table_sql(
|
|
430
|
+
table,
|
|
431
|
+
[@replicator.primary_key_column, @replicator.remote_key_column],
|
|
432
|
+
if_not_exists: true,
|
|
433
|
+
)
|
|
434
|
+
(@replicator.denormalized_columns + [@replicator.data_column]).each do |col|
|
|
435
|
+
schema_lines << adapter.add_column_sql(table, col, if_not_exists: true)
|
|
436
|
+
end
|
|
437
|
+
adapter_conn = adapter.connection(@connection_url)
|
|
438
|
+
schema_expr = schema_lines.join(";\n") + ";"
|
|
439
|
+
if schema_expr != self.sync_target.last_applied_schema
|
|
440
|
+
adapter_conn.execute(schema_expr)
|
|
441
|
+
self.sync_target.update(last_applied_schema: schema_expr)
|
|
442
|
+
end
|
|
443
|
+
tempfile = Tempfile.new("whdbsyncout-#{self.sync_target.id}")
|
|
444
|
+
begin
|
|
445
|
+
self.dataset_to_sync do |ds|
|
|
446
|
+
ds.db.copy_table(ds, options: "DELIMITER ',', HEADER true, FORMAT csv") do |row|
|
|
447
|
+
tempfile.write(row)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
tempfile.rewind
|
|
451
|
+
adapter.merge_from_csv(
|
|
452
|
+
adapter_conn,
|
|
453
|
+
tempfile,
|
|
454
|
+
table,
|
|
455
|
+
@replicator.primary_key_column,
|
|
456
|
+
[@replicator.primary_key_column,
|
|
457
|
+
@replicator.remote_key_column,] + @replicator.denormalized_columns + [@replicator.data_column],
|
|
458
|
+
)
|
|
459
|
+
self.record(self.now)
|
|
460
|
+
ensure
|
|
461
|
+
tempfile.unlink
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Table: sync_targets
|
|
468
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
469
|
+
# Columns:
|
|
470
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
471
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
472
|
+
# updated_at | timestamp with time zone |
|
|
473
|
+
# opaque_id | text | NOT NULL
|
|
474
|
+
# service_integration_id | integer | NOT NULL
|
|
475
|
+
# created_by_id | integer |
|
|
476
|
+
# period_seconds | integer | NOT NULL
|
|
477
|
+
# connection_url | text | NOT NULL
|
|
478
|
+
# schema | text | NOT NULL DEFAULT ''::text
|
|
479
|
+
# table | text | NOT NULL DEFAULT ''::text
|
|
480
|
+
# last_synced_at | timestamp with time zone |
|
|
481
|
+
# last_applied_schema | text | NOT NULL DEFAULT ''::text
|
|
482
|
+
# page_size | integer | NOT NULL
|
|
483
|
+
# Indexes:
|
|
484
|
+
# sync_targets_pkey | PRIMARY KEY btree (id)
|
|
485
|
+
# sync_targets_opaque_id_key | UNIQUE btree (opaque_id)
|
|
486
|
+
# sync_targets_last_synced_at_index | btree (last_synced_at)
|
|
487
|
+
# sync_targets_service_integration_id_index | btree (service_integration_id)
|
|
488
|
+
# Foreign key constraints:
|
|
489
|
+
# sync_targets_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
490
|
+
# sync_targets_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id) ON DELETE CASCADE
|
|
491
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake/tasklib"
|
|
4
|
+
|
|
5
|
+
require "webhookdb"
|
|
6
|
+
|
|
7
|
+
module Webhookdb::Tasks
|
|
8
|
+
class Admin < Rake::TaskLib
|
|
9
|
+
def initialize
|
|
10
|
+
super()
|
|
11
|
+
namespace :admin do
|
|
12
|
+
desc "Add roles to the named org"
|
|
13
|
+
task :role, [:org_key, :role] do |_, args|
|
|
14
|
+
self.setup
|
|
15
|
+
org = self.find_org(args)
|
|
16
|
+
role = Webhookdb::Role.find_or_create(name: args.fetch(:role))
|
|
17
|
+
org.add_feature_role(role)
|
|
18
|
+
puts "Added role #{role.name} to #{org.name}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task :backfill, [:org_key] do |_, args|
|
|
22
|
+
self.setup
|
|
23
|
+
org = self.find_org(args)
|
|
24
|
+
org.service_integrations.each do |sint|
|
|
25
|
+
Webhookdb::BackfillJob.create(service_integration: sint, incremental: false).enqueue
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task :connection, [:org_key, :type] do |_, args|
|
|
30
|
+
self.setup
|
|
31
|
+
org = self.find_org(args)
|
|
32
|
+
type = args.fetch(:type, "readonly")
|
|
33
|
+
puts org.send(:"#{type}_connection_url")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def setup
|
|
39
|
+
Webhookdb.load_app
|
|
40
|
+
Webhookdb::Async.setup_web if Amigo.subscribers.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def find_org(args)
|
|
44
|
+
org_key = args.fetch(:org_key)
|
|
45
|
+
org = Webhookdb::Organization[key: org_key] or raise "No org with key #{org_key}"
|
|
46
|
+
return org
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake/tasklib"
|
|
4
|
+
require "sequel"
|
|
5
|
+
|
|
6
|
+
require "webhookdb"
|
|
7
|
+
require "webhookdb/postgres"
|
|
8
|
+
|
|
9
|
+
module Webhookdb::Tasks
|
|
10
|
+
class Annotate < Rake::TaskLib
|
|
11
|
+
def initialize
|
|
12
|
+
super()
|
|
13
|
+
desc "Update model annotations"
|
|
14
|
+
task :annotate do
|
|
15
|
+
unless `git diff`.blank?
|
|
16
|
+
puts "Cannot annotate while there is any git diff."
|
|
17
|
+
puts "Please commit or revert any diff and try again."
|
|
18
|
+
exit(1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require "webhookdb"
|
|
22
|
+
Webhookdb.load_app
|
|
23
|
+
files = []
|
|
24
|
+
Webhookdb::Postgres.each_model_class do |cls|
|
|
25
|
+
files << "lib/#{cls.name.underscore}.rb" if cls.name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require "sequel/annotate"
|
|
29
|
+
Sequel::Annotate.annotate(files, border: true)
|
|
30
|
+
puts "Finished annotating:"
|
|
31
|
+
files.each { |f| puts " #{f}" }
|
|
32
|
+
puts "Please commit the changes."
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake/tasklib"
|
|
4
|
+
require "sequel"
|
|
5
|
+
|
|
6
|
+
require "webhookdb"
|
|
7
|
+
require "webhookdb/postgres"
|
|
8
|
+
|
|
9
|
+
module Webhookdb::Tasks
|
|
10
|
+
class DB < Rake::TaskLib
|
|
11
|
+
def initialize
|
|
12
|
+
super()
|
|
13
|
+
namespace :db do
|
|
14
|
+
desc "Drop all tables in the public schema."
|
|
15
|
+
task :drop_tables do
|
|
16
|
+
require "webhookdb/postgres"
|
|
17
|
+
Webhookdb::Postgres.load_superclasses
|
|
18
|
+
Webhookdb::Postgres.each_model_superclass do |sc|
|
|
19
|
+
sc.db[:pg_tables].where(schemaname: "public").each do |tbl|
|
|
20
|
+
self.exec(sc.db, "DROP TABLE #{tbl[:tablename]} CASCADE")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Remove all data from application schemas"
|
|
26
|
+
task :wipe do
|
|
27
|
+
require "webhookdb/postgres"
|
|
28
|
+
Webhookdb::Postgres.load_superclasses
|
|
29
|
+
Webhookdb::Postgres.each_model_class do |c|
|
|
30
|
+
c.truncate(cascade: true)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "Run migrations (rake db:migrate[<target>] to go to a specific version)"
|
|
35
|
+
task :migrate, [:version] do |_, args|
|
|
36
|
+
require "webhookdb/postgres"
|
|
37
|
+
Webhookdb::Postgres.load_superclasses
|
|
38
|
+
Webhookdb::Postgres.run_all_migrations(target: args[:version]&.to_i)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "Re-create the database tables. Drop tables and migrate."
|
|
42
|
+
task reset: ["db:drop_tables", "db:migrate"]
|
|
43
|
+
|
|
44
|
+
task :drop_replication_databases do
|
|
45
|
+
require "webhookdb/postgres"
|
|
46
|
+
Webhookdb::Postgres.load_superclasses
|
|
47
|
+
Webhookdb::Postgres.each_model_superclass do |c|
|
|
48
|
+
c.db[:pg_database].grep(:datname, "adb%").select(:datname).all.each do |row|
|
|
49
|
+
c.db << "DROP DATABASE #{row[:datname]}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
task drop_tables_and_replication_databases: ["db:drop_tables", "db:drop_replication_databases"]
|
|
55
|
+
|
|
56
|
+
task wipe_tables_and_drop_replication_databases: ["db:wipe", "db:drop_replication_databases"]
|
|
57
|
+
|
|
58
|
+
task :lookup_org_admin_url, [:org_id] do |_, args|
|
|
59
|
+
(orgid = args[:org_id]) or raise "Must provide org id as first argument"
|
|
60
|
+
require "webhookdb"
|
|
61
|
+
Webhookdb.load_app
|
|
62
|
+
org_cond = orgid.match?(/^\d$/) ? orgid.to_i : {key: orgid}
|
|
63
|
+
(org = Webhookdb::Organization[org_cond]) or raise "Org #{orgid} does not exist"
|
|
64
|
+
u = org.admin_connection_url
|
|
65
|
+
raise "Org #{orgid} has no connection url yet" if u.blank?
|
|
66
|
+
print(u)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def exec(db, cmd)
|
|
72
|
+
print cmd
|
|
73
|
+
begin
|
|
74
|
+
db.execute(cmd)
|
|
75
|
+
print "\n"
|
|
76
|
+
rescue StandardError
|
|
77
|
+
print " (error)\n"
|
|
78
|
+
raise
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake/tasklib"
|
|
4
|
+
require "sequel"
|
|
5
|
+
|
|
6
|
+
require "webhookdb"
|
|
7
|
+
require "webhookdb/postgres"
|
|
8
|
+
|
|
9
|
+
module Webhookdb::Tasks
|
|
10
|
+
class Docs < Rake::TaskLib
|
|
11
|
+
def initialize
|
|
12
|
+
super()
|
|
13
|
+
namespace :docs do
|
|
14
|
+
desc "Write out auto-generated docs for integrations."
|
|
15
|
+
task :replicators, [:out, :name] do |_, args|
|
|
16
|
+
(out = args[:out]) or raise ArgumentError, "must pass :out param (directory to write files)"
|
|
17
|
+
require "webhookdb/replicator"
|
|
18
|
+
Webhookdb.load_app
|
|
19
|
+
if (rname = args[:name])
|
|
20
|
+
repl = Webhookdb::Replicator.registered!(rname)
|
|
21
|
+
puts self.replicator_md(repl)
|
|
22
|
+
else
|
|
23
|
+
descriptors = Webhookdb::Replicator::Docgen.documentable_descriptors
|
|
24
|
+
descriptors.each do |repl|
|
|
25
|
+
md = self.replicator_md(repl)
|
|
26
|
+
path = File.join(out, "#{repl.name}.md")
|
|
27
|
+
File.write(path, md)
|
|
28
|
+
end
|
|
29
|
+
list_md = Webhookdb::Replicator::Docgen.replicator_list_md(descriptors)
|
|
30
|
+
list_path = File.join(out, "../_includes/replicator_list.md")
|
|
31
|
+
File.write(list_path, list_md)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param desc [Webhookdb::Replicator::Descriptor]
|
|
38
|
+
def replicator_md(desc)
|
|
39
|
+
return Webhookdb::Replicator::Docgen.new(desc).markdown
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|