webhookdb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/data/messages/layouts/blank.email.liquid +10 -0
- data/data/messages/layouts/minimal.email.liquid +28 -0
- data/data/messages/layouts/standard.email.liquid +28 -0
- data/data/messages/partials/button.liquid +15 -0
- data/data/messages/partials/environment_banner.liquid +9 -0
- data/data/messages/partials/footer.liquid +22 -0
- data/data/messages/partials/greeting.liquid +3 -0
- data/data/messages/partials/logo_header.liquid +18 -0
- data/data/messages/partials/signoff.liquid +1 -0
- data/data/messages/styles/v1.liquid +346 -0
- data/data/messages/templates/errors/icalendar_fetch.email.liquid +29 -0
- data/data/messages/templates/invite.email.liquid +15 -0
- data/data/messages/templates/new_customer.email.liquid +24 -0
- data/data/messages/templates/org_database_migration_finished.email.liquid +7 -0
- data/data/messages/templates/org_database_migration_started.email.liquid +9 -0
- data/data/messages/templates/specs/_field_partial.liquid +1 -0
- data/data/messages/templates/specs/basic.email.liquid +2 -0
- data/data/messages/templates/specs/basic.fake.liquid +1 -0
- data/data/messages/templates/specs/with_field.email.liquid +2 -0
- data/data/messages/templates/specs/with_field.fake.liquid +1 -0
- data/data/messages/templates/specs/with_include.email.liquid +2 -0
- data/data/messages/templates/specs/with_partial.email.liquid +1 -0
- data/data/messages/templates/verification.email.liquid +14 -0
- data/data/messages/templates/verification.sms.liquid +1 -0
- data/data/messages/web/install-customer-login.liquid +48 -0
- data/data/messages/web/install-error.liquid +17 -0
- data/data/messages/web/install-success.liquid +35 -0
- data/data/messages/web/install.liquid +20 -0
- data/data/messages/web/partials/footer.liquid +4 -0
- data/data/messages/web/partials/form_error.liquid +1 -0
- data/data/messages/web/partials/header.liquid +3 -0
- data/data/messages/web/styles.liquid +134 -0
- data/data/windows_tz.txt +461 -0
- data/db/migrations/001_testing_pixies.rb +13 -0
- data/db/migrations/002_initial.rb +132 -0
- data/db/migrations/003_ux_overhaul.rb +20 -0
- data/db/migrations/004_incremental_backfill.rb +9 -0
- data/db/migrations/005_log_webhooks.rb +24 -0
- data/db/migrations/006_generalize_roles.rb +29 -0
- data/db/migrations/007_org_dns.rb +12 -0
- data/db/migrations/008_webhook_subscriptions.rb +19 -0
- data/db/migrations/009_nonunique_stripe_subscription_customer.rb +16 -0
- data/db/migrations/010_drop_integration_soft_delete.rb +14 -0
- data/db/migrations/011_webhook_subscriptions_created_at.rb +10 -0
- data/db/migrations/012_webhook_subscriptions_created_by.rb +9 -0
- data/db/migrations/013_default_org_membership.rb +30 -0
- data/db/migrations/014_webhook_subscription_deliveries.rb +26 -0
- data/db/migrations/015_dependent_integrations.rb +9 -0
- data/db/migrations/016_encrypted_columns.rb +9 -0
- data/db/migrations/017_skip_verification.rb +9 -0
- data/db/migrations/018_sync_targets.rb +25 -0
- data/db/migrations/019_org_schema.rb +9 -0
- data/db/migrations/020_org_database_migrations.rb +25 -0
- data/db/migrations/021_no_default_org_schema.rb +14 -0
- data/db/migrations/022_database_document.rb +15 -0
- data/db/migrations/023_sync_target_schema.rb +9 -0
- data/db/migrations/024_org_semaphore_jobs.rb +9 -0
- data/db/migrations/025_integration_backfill_cursor.rb +9 -0
- data/db/migrations/026_undo_integration_backfill_cursor.rb +9 -0
- data/db/migrations/027_sync_target_http_sync.rb +12 -0
- data/db/migrations/028_logged_webhook_path.rb +24 -0
- data/db/migrations/029_encrypt_columns.rb +97 -0
- data/db/migrations/030_org_sync_target_timeout.rb +9 -0
- data/db/migrations/031_org_max_query_rows.rb +9 -0
- data/db/migrations/032_remove_db_defaults.rb +12 -0
- data/db/migrations/033_backfill_jobs.rb +26 -0
- data/db/migrations/034_backfill_job_criteria.rb +9 -0
- data/db/migrations/035_synchronous_backfill.rb +9 -0
- data/db/migrations/036_oauth.rb +26 -0
- data/db/migrations/037_oauth_used.rb +9 -0
- data/lib/amigo/durable_job.rb +416 -0
- data/lib/pry/clipboard.rb +111 -0
- data/lib/sequel/advisory_lock.rb +65 -0
- data/lib/webhookdb/admin.rb +4 -0
- data/lib/webhookdb/admin_api/auth.rb +36 -0
- data/lib/webhookdb/admin_api/customers.rb +63 -0
- data/lib/webhookdb/admin_api/database_documents.rb +20 -0
- data/lib/webhookdb/admin_api/entities.rb +66 -0
- data/lib/webhookdb/admin_api/message_deliveries.rb +61 -0
- data/lib/webhookdb/admin_api/roles.rb +15 -0
- data/lib/webhookdb/admin_api.rb +34 -0
- data/lib/webhookdb/aggregate_result.rb +63 -0
- data/lib/webhookdb/api/auth.rb +122 -0
- data/lib/webhookdb/api/connstr_auth.rb +36 -0
- data/lib/webhookdb/api/db.rb +188 -0
- data/lib/webhookdb/api/demo.rb +14 -0
- data/lib/webhookdb/api/entities.rb +198 -0
- data/lib/webhookdb/api/helpers.rb +253 -0
- data/lib/webhookdb/api/install.rb +296 -0
- data/lib/webhookdb/api/me.rb +53 -0
- data/lib/webhookdb/api/organizations.rb +254 -0
- data/lib/webhookdb/api/replay.rb +64 -0
- data/lib/webhookdb/api/service_integrations.rb +402 -0
- data/lib/webhookdb/api/services.rb +27 -0
- data/lib/webhookdb/api/stripe.rb +22 -0
- data/lib/webhookdb/api/subscriptions.rb +67 -0
- data/lib/webhookdb/api/sync_targets.rb +232 -0
- data/lib/webhookdb/api/system.rb +37 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +96 -0
- data/lib/webhookdb/api.rb +92 -0
- data/lib/webhookdb/apps.rb +93 -0
- data/lib/webhookdb/async/audit_logger.rb +38 -0
- data/lib/webhookdb/async/autoscaler.rb +84 -0
- data/lib/webhookdb/async/job.rb +18 -0
- data/lib/webhookdb/async/job_logger.rb +45 -0
- data/lib/webhookdb/async/scheduled_job.rb +18 -0
- data/lib/webhookdb/async.rb +142 -0
- data/lib/webhookdb/aws.rb +98 -0
- data/lib/webhookdb/backfill_job.rb +107 -0
- data/lib/webhookdb/backfiller.rb +107 -0
- data/lib/webhookdb/cloudflare.rb +39 -0
- data/lib/webhookdb/connection_cache.rb +177 -0
- data/lib/webhookdb/console.rb +71 -0
- data/lib/webhookdb/convertkit.rb +14 -0
- data/lib/webhookdb/crypto.rb +66 -0
- data/lib/webhookdb/customer/reset_code.rb +94 -0
- data/lib/webhookdb/customer.rb +347 -0
- data/lib/webhookdb/database_document.rb +72 -0
- data/lib/webhookdb/db_adapter/column_types.rb +37 -0
- data/lib/webhookdb/db_adapter/default_sql.rb +187 -0
- data/lib/webhookdb/db_adapter/pg.rb +96 -0
- data/lib/webhookdb/db_adapter/snowflake.rb +137 -0
- data/lib/webhookdb/db_adapter.rb +208 -0
- data/lib/webhookdb/dbutil.rb +92 -0
- data/lib/webhookdb/demo_mode.rb +100 -0
- data/lib/webhookdb/developer_alert.rb +51 -0
- data/lib/webhookdb/email_octopus.rb +21 -0
- data/lib/webhookdb/enumerable.rb +18 -0
- data/lib/webhookdb/fixtures/backfill_jobs.rb +72 -0
- data/lib/webhookdb/fixtures/customers.rb +65 -0
- data/lib/webhookdb/fixtures/database_documents.rb +27 -0
- data/lib/webhookdb/fixtures/faker.rb +41 -0
- data/lib/webhookdb/fixtures/logged_webhooks.rb +56 -0
- data/lib/webhookdb/fixtures/message_deliveries.rb +59 -0
- data/lib/webhookdb/fixtures/oauth_sessions.rb +24 -0
- data/lib/webhookdb/fixtures/organization_database_migrations.rb +37 -0
- data/lib/webhookdb/fixtures/organization_memberships.rb +54 -0
- data/lib/webhookdb/fixtures/organizations.rb +32 -0
- data/lib/webhookdb/fixtures/reset_codes.rb +23 -0
- data/lib/webhookdb/fixtures/service_integrations.rb +42 -0
- data/lib/webhookdb/fixtures/subscriptions.rb +33 -0
- data/lib/webhookdb/fixtures/sync_targets.rb +32 -0
- data/lib/webhookdb/fixtures/webhook_subscriptions.rb +35 -0
- data/lib/webhookdb/fixtures.rb +15 -0
- data/lib/webhookdb/formatting.rb +56 -0
- data/lib/webhookdb/front.rb +49 -0
- data/lib/webhookdb/github.rb +22 -0
- data/lib/webhookdb/google_calendar.rb +29 -0
- data/lib/webhookdb/heroku.rb +21 -0
- data/lib/webhookdb/http.rb +114 -0
- data/lib/webhookdb/icalendar.rb +17 -0
- data/lib/webhookdb/id.rb +17 -0
- data/lib/webhookdb/idempotency.rb +90 -0
- data/lib/webhookdb/increase.rb +42 -0
- data/lib/webhookdb/intercom.rb +23 -0
- data/lib/webhookdb/jobs/amigo_test_jobs.rb +118 -0
- data/lib/webhookdb/jobs/backfill.rb +32 -0
- data/lib/webhookdb/jobs/create_mirror_table.rb +18 -0
- data/lib/webhookdb/jobs/create_stripe_customer.rb +17 -0
- data/lib/webhookdb/jobs/customer_created_notify_internal.rb +22 -0
- data/lib/webhookdb/jobs/demo_mode_sync_data.rb +19 -0
- data/lib/webhookdb/jobs/deprecated_jobs.rb +19 -0
- data/lib/webhookdb/jobs/developer_alert_handle.rb +14 -0
- data/lib/webhookdb/jobs/durable_job_recheck_poller.rb +17 -0
- data/lib/webhookdb/jobs/emailer.rb +15 -0
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +25 -0
- data/lib/webhookdb/jobs/icalendar_sync.rb +23 -0
- data/lib/webhookdb/jobs/logged_webhook_replay.rb +17 -0
- data/lib/webhookdb/jobs/logged_webhook_resilient_replay.rb +15 -0
- data/lib/webhookdb/jobs/message_dispatched.rb +16 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_run.rb +24 -0
- data/lib/webhookdb/jobs/prepare_database_connections.rb +22 -0
- data/lib/webhookdb/jobs/process_webhook.rb +47 -0
- data/lib/webhookdb/jobs/renew_watch_channel.rb +24 -0
- data/lib/webhookdb/jobs/replication_migration.rb +24 -0
- data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +23 -0
- data/lib/webhookdb/jobs/scheduled_backfills.rb +77 -0
- data/lib/webhookdb/jobs/send_invite.rb +15 -0
- data/lib/webhookdb/jobs/send_test_webhook.rb +25 -0
- data/lib/webhookdb/jobs/send_webhook.rb +20 -0
- data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +16 -0
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +38 -0
- data/lib/webhookdb/jobs/trim_logged_webhooks.rb +15 -0
- data/lib/webhookdb/jobs/webhook_resource_notify_integrations.rb +30 -0
- data/lib/webhookdb/jobs/webhook_subscription_delivery_attempt.rb +29 -0
- data/lib/webhookdb/jobs.rb +4 -0
- data/lib/webhookdb/json.rb +113 -0
- data/lib/webhookdb/liquid/expose.rb +27 -0
- data/lib/webhookdb/liquid/filters.rb +16 -0
- data/lib/webhookdb/liquid/liquification.rb +26 -0
- data/lib/webhookdb/liquid/partial.rb +12 -0
- data/lib/webhookdb/logged_webhook/resilient.rb +95 -0
- data/lib/webhookdb/logged_webhook.rb +194 -0
- data/lib/webhookdb/message/body.rb +25 -0
- data/lib/webhookdb/message/delivery.rb +127 -0
- data/lib/webhookdb/message/email_transport.rb +133 -0
- data/lib/webhookdb/message/fake_transport.rb +54 -0
- data/lib/webhookdb/message/liquid_drops.rb +29 -0
- data/lib/webhookdb/message/template.rb +89 -0
- data/lib/webhookdb/message/transport.rb +43 -0
- data/lib/webhookdb/message.rb +150 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +42 -0
- data/lib/webhookdb/messages/invite.rb +23 -0
- data/lib/webhookdb/messages/new_customer.rb +14 -0
- data/lib/webhookdb/messages/org_database_migration_finished.rb +23 -0
- data/lib/webhookdb/messages/org_database_migration_started.rb +24 -0
- data/lib/webhookdb/messages/specs.rb +57 -0
- data/lib/webhookdb/messages/verification.rb +23 -0
- data/lib/webhookdb/method_utilities.rb +82 -0
- data/lib/webhookdb/microsoft_calendar.rb +36 -0
- data/lib/webhookdb/nextpax.rb +14 -0
- data/lib/webhookdb/oauth/front.rb +58 -0
- data/lib/webhookdb/oauth/intercom.rb +58 -0
- data/lib/webhookdb/oauth/session.rb +24 -0
- data/lib/webhookdb/oauth.rb +80 -0
- data/lib/webhookdb/organization/alerting.rb +35 -0
- data/lib/webhookdb/organization/database_migration.rb +151 -0
- data/lib/webhookdb/organization/db_builder.rb +429 -0
- data/lib/webhookdb/organization.rb +506 -0
- data/lib/webhookdb/organization_membership.rb +58 -0
- data/lib/webhookdb/phone_number.rb +38 -0
- data/lib/webhookdb/plaid.rb +23 -0
- data/lib/webhookdb/platform.rb +27 -0
- data/lib/webhookdb/plivo.rb +52 -0
- data/lib/webhookdb/postgres/maintenance.rb +166 -0
- data/lib/webhookdb/postgres/model.rb +82 -0
- data/lib/webhookdb/postgres/model_utilities.rb +382 -0
- data/lib/webhookdb/postgres/testing_pixie.rb +16 -0
- data/lib/webhookdb/postgres/validations.rb +46 -0
- data/lib/webhookdb/postgres.rb +176 -0
- data/lib/webhookdb/postmark.rb +20 -0
- data/lib/webhookdb/redis.rb +35 -0
- data/lib/webhookdb/replicator/atom_single_feed_v1.rb +116 -0
- data/lib/webhookdb/replicator/aws_pricing_v1.rb +488 -0
- data/lib/webhookdb/replicator/base.rb +1185 -0
- data/lib/webhookdb/replicator/column.rb +482 -0
- data/lib/webhookdb/replicator/convertkit_broadcast_v1.rb +69 -0
- data/lib/webhookdb/replicator/convertkit_subscriber_v1.rb +200 -0
- data/lib/webhookdb/replicator/convertkit_tag_v1.rb +66 -0
- data/lib/webhookdb/replicator/convertkit_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/docgen.rb +167 -0
- data/lib/webhookdb/replicator/email_octopus_campaign_v1.rb +84 -0
- data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +159 -0
- data/lib/webhookdb/replicator/email_octopus_event_v1.rb +244 -0
- data/lib/webhookdb/replicator/email_octopus_list_v1.rb +101 -0
- data/lib/webhookdb/replicator/fake.rb +453 -0
- data/lib/webhookdb/replicator/front_conversation_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_marketplace_root_v1.rb +55 -0
- data/lib/webhookdb/replicator/front_message_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_v1_mixin.rb +22 -0
- data/lib/webhookdb/replicator/github_issue_comment_v1.rb +58 -0
- data/lib/webhookdb/replicator/github_issue_v1.rb +83 -0
- data/lib/webhookdb/replicator/github_pull_v1.rb +84 -0
- data/lib/webhookdb/replicator/github_release_v1.rb +47 -0
- data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +250 -0
- data/lib/webhookdb/replicator/github_repository_event_v1.rb +45 -0
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +465 -0
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +334 -0
- data/lib/webhookdb/replicator/increase_account_number_v1.rb +77 -0
- data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/increase_account_v1.rb +63 -0
- data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +64 -0
- data/lib/webhookdb/replicator/increase_limit_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_transaction_v1.rb +74 -0
- data/lib/webhookdb/replicator/increase_v1_mixin.rb +121 -0
- data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +36 -0
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +38 -0
- data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +69 -0
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +105 -0
- data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +65 -0
- data/lib/webhookdb/replicator/plivo_sms_inbound_v1.rb +102 -0
- data/lib/webhookdb/replicator/postmark_inbound_message_v1.rb +94 -0
- data/lib/webhookdb/replicator/postmark_outbound_message_event_v1.rb +107 -0
- data/lib/webhookdb/replicator/schema_modification.rb +42 -0
- data/lib/webhookdb/replicator/shopify_customer_v1.rb +58 -0
- data/lib/webhookdb/replicator/shopify_order_v1.rb +64 -0
- data/lib/webhookdb/replicator/shopify_v1_mixin.rb +161 -0
- data/lib/webhookdb/replicator/signalwire_message_v1.rb +169 -0
- data/lib/webhookdb/replicator/sponsy_customer_v1.rb +54 -0
- data/lib/webhookdb/replicator/sponsy_placement_v1.rb +34 -0
- data/lib/webhookdb/replicator/sponsy_publication_v1.rb +125 -0
- data/lib/webhookdb/replicator/sponsy_slot_v1.rb +41 -0
- data/lib/webhookdb/replicator/sponsy_status_v1.rb +35 -0
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +165 -0
- data/lib/webhookdb/replicator/state_machine_step.rb +69 -0
- data/lib/webhookdb/replicator/stripe_charge_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_coupon_v1.rb +62 -0
- data/lib/webhookdb/replicator/stripe_customer_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_dispute_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_invoice_item_v1.rb +82 -0
- data/lib/webhookdb/replicator/stripe_invoice_v1.rb +116 -0
- data/lib/webhookdb/replicator/stripe_payout_v1.rb +67 -0
- data/lib/webhookdb/replicator/stripe_price_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_product_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_refund_v1.rb +101 -0
- data/lib/webhookdb/replicator/stripe_subscription_item_v1.rb +56 -0
- data/lib/webhookdb/replicator/stripe_subscription_v1.rb +75 -0
- data/lib/webhookdb/replicator/stripe_v1_mixin.rb +116 -0
- data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +141 -0
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +169 -0
- data/lib/webhookdb/replicator/transistor_show_v1.rb +68 -0
- data/lib/webhookdb/replicator/transistor_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/twilio_sms_v1.rb +156 -0
- data/lib/webhookdb/replicator/webhook_request.rb +5 -0
- data/lib/webhookdb/replicator/webhookdb_customer_v1.rb +74 -0
- data/lib/webhookdb/replicator.rb +224 -0
- data/lib/webhookdb/role.rb +42 -0
- data/lib/webhookdb/sentry.rb +35 -0
- data/lib/webhookdb/service/auth.rb +138 -0
- data/lib/webhookdb/service/collection.rb +91 -0
- data/lib/webhookdb/service/entities.rb +97 -0
- data/lib/webhookdb/service/helpers.rb +270 -0
- data/lib/webhookdb/service/middleware.rb +124 -0
- data/lib/webhookdb/service/types.rb +30 -0
- data/lib/webhookdb/service/validators.rb +32 -0
- data/lib/webhookdb/service/view_api.rb +63 -0
- data/lib/webhookdb/service.rb +219 -0
- data/lib/webhookdb/service_integration.rb +332 -0
- data/lib/webhookdb/shopify.rb +35 -0
- data/lib/webhookdb/signalwire.rb +13 -0
- data/lib/webhookdb/slack.rb +68 -0
- data/lib/webhookdb/snowflake.rb +90 -0
- data/lib/webhookdb/spec_helpers/async.rb +122 -0
- data/lib/webhookdb/spec_helpers/citest.rb +88 -0
- data/lib/webhookdb/spec_helpers/integration.rb +121 -0
- data/lib/webhookdb/spec_helpers/message.rb +41 -0
- data/lib/webhookdb/spec_helpers/postgres.rb +220 -0
- data/lib/webhookdb/spec_helpers/service.rb +432 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_columns.rb +56 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +915 -0
- data/lib/webhookdb/spec_helpers/whdb.rb +139 -0
- data/lib/webhookdb/spec_helpers.rb +63 -0
- data/lib/webhookdb/sponsy.rb +14 -0
- data/lib/webhookdb/stripe.rb +37 -0
- data/lib/webhookdb/subscription.rb +203 -0
- data/lib/webhookdb/sync_target.rb +491 -0
- data/lib/webhookdb/tasks/admin.rb +49 -0
- data/lib/webhookdb/tasks/annotate.rb +36 -0
- data/lib/webhookdb/tasks/db.rb +82 -0
- data/lib/webhookdb/tasks/docs.rb +42 -0
- data/lib/webhookdb/tasks/fixture.rb +35 -0
- data/lib/webhookdb/tasks/message.rb +50 -0
- data/lib/webhookdb/tasks/regress.rb +87 -0
- data/lib/webhookdb/tasks/release.rb +27 -0
- data/lib/webhookdb/tasks/sidekiq.rb +23 -0
- data/lib/webhookdb/tasks/specs.rb +64 -0
- data/lib/webhookdb/theranest.rb +15 -0
- data/lib/webhookdb/transistor.rb +13 -0
- data/lib/webhookdb/twilio.rb +13 -0
- data/lib/webhookdb/typed_struct.rb +44 -0
- data/lib/webhookdb/version.rb +5 -0
- data/lib/webhookdb/webhook_response.rb +50 -0
- data/lib/webhookdb/webhook_subscription/delivery.rb +82 -0
- data/lib/webhookdb/webhook_subscription.rb +226 -0
- data/lib/webhookdb/windows_tz.rb +32 -0
- data/lib/webhookdb/xml.rb +92 -0
- data/lib/webhookdb.rb +224 -0
- data/lib/webterm/apps.rb +45 -0
- metadata +1129 -0
@@ -0,0 +1,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
|