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,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pg"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/db_adapter/default_sql"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::DBAdapter::PG < Webhookdb::DBAdapter
|
|
8
|
+
include Webhookdb::DBAdapter::ColumnTypes
|
|
9
|
+
include Webhookdb::DBAdapter::DefaultSql
|
|
10
|
+
|
|
11
|
+
VERIFY_TIMEOUT = 2
|
|
12
|
+
VERIFY_STATEMENT = "SELECT 1"
|
|
13
|
+
|
|
14
|
+
def identifier_quote_char
|
|
15
|
+
return '"'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_index_sql(index, concurrently:)
|
|
19
|
+
tgts = index.targets.map { |c| self.escape_identifier(c.name) }.join(", ")
|
|
20
|
+
uniq = index.unique ? " UNIQUE" : ""
|
|
21
|
+
concurrent = concurrently ? " CONCURRENTLY" : ""
|
|
22
|
+
idxname = self.escape_identifier(index.name)
|
|
23
|
+
tblname = self.qualify_table(index.table)
|
|
24
|
+
where = ""
|
|
25
|
+
where = " " + Webhookdb::Customer.where(index.where).sql.delete_prefix('SELECT * FROM "customers" ') if index.where
|
|
26
|
+
return "CREATE#{uniq} INDEX#{concurrent} IF NOT EXISTS #{idxname} ON #{tblname} (#{tgts})#{where}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def column_create_sql(column)
|
|
30
|
+
modifiers = +""
|
|
31
|
+
coltype = COLTYPE_MAP.fetch(column.type)
|
|
32
|
+
if column.pk?
|
|
33
|
+
coltype = "bigserial" if column.type == BIGINT
|
|
34
|
+
coltype = "serial" if column.type == INTEGER
|
|
35
|
+
modifiers << " PRIMARY KEY"
|
|
36
|
+
elsif column.unique?
|
|
37
|
+
modifiers << " UNIQUE NOT NULL"
|
|
38
|
+
elsif !column.nullable?
|
|
39
|
+
modifiers << " NOT NULL"
|
|
40
|
+
end
|
|
41
|
+
colname = self.escape_identifier(column.name)
|
|
42
|
+
return "#{colname} #{coltype}#{modifiers}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_column_sql(table, column, if_not_exists: false)
|
|
46
|
+
c = self.column_create_sql(column)
|
|
47
|
+
ifne = if_not_exists ? " IF NOT EXISTS" : ""
|
|
48
|
+
return "ALTER TABLE #{self.qualify_table(table)} ADD COLUMN#{ifne} #{c}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def merge_from_csv(connection, file, table, pk_col, copy_columns)
|
|
52
|
+
qtable = self.qualify_table(table)
|
|
53
|
+
temptable = "#{self.escape_identifier(table.name)}_staging_#{SecureRandom.hex(4)}"
|
|
54
|
+
connection.using do |db|
|
|
55
|
+
db << "CREATE TEMP TABLE #{temptable} (LIKE #{qtable})"
|
|
56
|
+
db.copy_into(temptable.to_sym, options: "DELIMITER ',', HEADER true, FORMAT csv", data: file)
|
|
57
|
+
pkname = self.escape_identifier(pk_col.name)
|
|
58
|
+
col_assigns = self.assign_columns_sql("src", nil, copy_columns)
|
|
59
|
+
upsert_sql = [
|
|
60
|
+
<<~UPDATE,
|
|
61
|
+
UPDATE #{qtable} AS tgt
|
|
62
|
+
SET #{col_assigns} FROM
|
|
63
|
+
(SELECT * FROM #{temptable} WHERE #{pkname} IN (SELECT #{pkname} FROM #{qtable})) src
|
|
64
|
+
WHERE tgt.#{pkname} = src.#{pkname};
|
|
65
|
+
UPDATE
|
|
66
|
+
"INSERT INTO #{qtable} SELECT * FROM #{temptable} WHERE #{pkname} NOT IN (SELECT #{pkname} FROM #{qtable});",
|
|
67
|
+
]
|
|
68
|
+
db << upsert_sql.join("\n")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def verify_connection(url, timeout: 2, statement: "SELECT 1")
|
|
73
|
+
conn = self.connection(url)
|
|
74
|
+
conn.using(connect_timeout: timeout) do |c|
|
|
75
|
+
c.execute("SET statement_timeout TO #{timeout * 1000}")
|
|
76
|
+
c.execute(statement)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
COLTYPE_MAP = {
|
|
81
|
+
BIGINT => "bigint",
|
|
82
|
+
BIGINT_ARRAY => "bigint[]",
|
|
83
|
+
BOOLEAN => "boolean",
|
|
84
|
+
DATE => "date",
|
|
85
|
+
DECIMAL => "numeric",
|
|
86
|
+
DOUBLE => "double precision",
|
|
87
|
+
FLOAT => "float",
|
|
88
|
+
INTEGER => "integer",
|
|
89
|
+
INTEGER_ARRAY => "integer[]",
|
|
90
|
+
OBJECT => "jsonb",
|
|
91
|
+
TEXT => "text",
|
|
92
|
+
TEXT_ARRAY => "text[]",
|
|
93
|
+
TIMESTAMP => "timestamptz",
|
|
94
|
+
UUID => "uuid",
|
|
95
|
+
}.freeze
|
|
96
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/db_adapter/default_sql"
|
|
4
|
+
require "webhookdb/snowflake"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::DBAdapter::Snowflake < Webhookdb::DBAdapter
|
|
7
|
+
include Webhookdb::DBAdapter::ColumnTypes
|
|
8
|
+
include Webhookdb::DBAdapter::DefaultSql
|
|
9
|
+
|
|
10
|
+
class SnowsqlConnection < Webhookdb::DBAdapter::Connection
|
|
11
|
+
include Appydays::Loggable
|
|
12
|
+
|
|
13
|
+
def initialize(url)
|
|
14
|
+
super()
|
|
15
|
+
@url = url
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute(sql, **)
|
|
19
|
+
self.logger.debug("snowflake_exec", statement: sql)
|
|
20
|
+
result = Webhookdb::Snowflake.run_cli(@url, sql, **)
|
|
21
|
+
self.logger.debug("snowflake_exec_result", result:)
|
|
22
|
+
return result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def connection(url)
|
|
27
|
+
return SnowsqlConnection.new(url)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_index_sql(*)
|
|
31
|
+
raise NotImplementedError, "Snowflake does not support indices"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def column_create_sql(column)
|
|
35
|
+
modifiers = +""
|
|
36
|
+
if column.unique?
|
|
37
|
+
modifiers << " UNIQUE NOT NULL"
|
|
38
|
+
elsif !column.nullable?
|
|
39
|
+
modifiers << " NOT NULL"
|
|
40
|
+
end
|
|
41
|
+
coltype = COLTYPE_MAP.fetch(column.type)
|
|
42
|
+
colname = self.escape_identifier(column.name)
|
|
43
|
+
return "#{colname} #{coltype}#{modifiers}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_column_sql(table, column, if_not_exists: false)
|
|
47
|
+
c = self.column_create_sql(column)
|
|
48
|
+
# Snowflake has no 'ADD COLUMN IF NOT EXISTS' so we need to query the long way around
|
|
49
|
+
add_sql = "ALTER TABLE #{self.qualify_table(table)} ADD COLUMN #{c}"
|
|
50
|
+
return add_sql unless if_not_exists
|
|
51
|
+
# The 'ILIKE' is a case-insensitive string compare,
|
|
52
|
+
# which is important because snowflake uppercases values when it stores them.
|
|
53
|
+
conditional_sql = <<~SQL
|
|
54
|
+
EXECUTE IMMEDIATE $$
|
|
55
|
+
BEGIN
|
|
56
|
+
IF (NOT EXISTS(
|
|
57
|
+
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
|
|
58
|
+
WHERE TABLE_SCHEMA ILIKE '#{table.schema.name}'
|
|
59
|
+
AND TABLE_NAME ILIKE '#{table.name}'
|
|
60
|
+
AND COLUMN_NAME ILIKE '#{column.name}'
|
|
61
|
+
)) THEN
|
|
62
|
+
#{add_sql};
|
|
63
|
+
END IF;
|
|
64
|
+
END;
|
|
65
|
+
$$
|
|
66
|
+
SQL
|
|
67
|
+
return conditional_sql.rstrip
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def merge_from_csv(connection, file, table, pk_col, copy_columns)
|
|
71
|
+
raise Webhookdb::InvalidPrecondition, "table must have schema" if table.schema.nil?
|
|
72
|
+
|
|
73
|
+
qtable = self.qualify_table(table)
|
|
74
|
+
|
|
75
|
+
stage = self.escape_identifier("whdb_tempstage_#{SecureRandom.hex(2)}_#{table.name}")
|
|
76
|
+
stage = self.escape_identifier(table.schema.name) + "." + stage
|
|
77
|
+
|
|
78
|
+
pkname = self.escape_identifier(pk_col.name)
|
|
79
|
+
# JSON columns need to be parsed from the CSV, so object columns need parse_json calls.
|
|
80
|
+
col_assigns = self.assign_columns_sql("src", nil, copy_columns) do |c, lhs, rhs|
|
|
81
|
+
if c.type == OBJECT
|
|
82
|
+
[lhs, "parse_json(#{rhs})"]
|
|
83
|
+
else
|
|
84
|
+
[lhs, rhs]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
col_names = copy_columns.map { |c| self.escape_identifier(c.name) }
|
|
88
|
+
col_values = col_names.each_with_index.map do |n, i|
|
|
89
|
+
if copy_columns[i].type == OBJECT
|
|
90
|
+
"parse_json(src.#{n})"
|
|
91
|
+
else
|
|
92
|
+
"src.#{n}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
col_placeholders = col_names.each_with_index.map { |n, i| "$#{i + 1} #{n}" }
|
|
96
|
+
# Props to https://stackoverflow.com/questions/63084511/snowflake-upsert-from-staged-files
|
|
97
|
+
# for the merge from stage code.
|
|
98
|
+
# The enclosed option is vital because otherwise it doesn't interpret JSON columns properly.
|
|
99
|
+
import_sql = <<~SQL
|
|
100
|
+
CREATE STAGE #{stage} FILE_FORMAT = (type = 'CSV' skip_header = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '"');
|
|
101
|
+
|
|
102
|
+
PUT file://#{file.path} @#{stage} auto_compress=true;
|
|
103
|
+
|
|
104
|
+
MERGE INTO #{qtable} AS tgt
|
|
105
|
+
USING (
|
|
106
|
+
SELECT #{col_placeholders.join(', ')} FROM @#{stage}
|
|
107
|
+
) src
|
|
108
|
+
ON tgt.#{pkname} = src.#{pkname}
|
|
109
|
+
WHEN MATCHED THEN UPDATE SET #{col_assigns}
|
|
110
|
+
WHEN NOT MATCHED THEN INSERT (#{col_names.join(', ')}) values (#{col_values.join(', ')});
|
|
111
|
+
SQL
|
|
112
|
+
connection.execute(import_sql)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _verify_connection(url, timeout:, statement:)
|
|
116
|
+
_ = timeout
|
|
117
|
+
conn = self.connection(url)
|
|
118
|
+
conn.execute(statement)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def identifier_quote_char
|
|
122
|
+
return ""
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
COLTYPE_MAP = {
|
|
126
|
+
BIGINT => "bigint",
|
|
127
|
+
BOOLEAN => "boolean",
|
|
128
|
+
DATE => "date",
|
|
129
|
+
DECIMAL => "numeric",
|
|
130
|
+
DOUBLE => "double precision",
|
|
131
|
+
FLOAT => "float",
|
|
132
|
+
INTEGER => "integer",
|
|
133
|
+
OBJECT => "object",
|
|
134
|
+
TEXT => "text",
|
|
135
|
+
TIMESTAMP => "timestamptz",
|
|
136
|
+
}.freeze
|
|
137
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::DBAdapter
|
|
4
|
+
require "webhookdb/db_adapter/column_types"
|
|
5
|
+
|
|
6
|
+
class UnsupportedAdapter < StandardError; end
|
|
7
|
+
|
|
8
|
+
VALID_IDENTIFIER = /^[a-zA-Z][a-zA-Z\d_ ]*$/
|
|
9
|
+
INVALID_IDENTIFIER_MESSAGE = "Identifiers must start with a letter and " \
|
|
10
|
+
"contain only letters, numbers, spaces, and underscores. " \
|
|
11
|
+
"See https://docs.webhookdb.com/concepts/valid-identifiers/ for rules " \
|
|
12
|
+
"about identifiers like schema, table, and column names."
|
|
13
|
+
|
|
14
|
+
class Schema < Webhookdb::TypedStruct
|
|
15
|
+
attr_reader :name
|
|
16
|
+
|
|
17
|
+
def initialize(**kwargs)
|
|
18
|
+
super
|
|
19
|
+
self.typecheck!(:name, Symbol)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Table < Webhookdb::TypedStruct
|
|
24
|
+
attr_reader :name, :schema
|
|
25
|
+
|
|
26
|
+
def initialize(**kwargs)
|
|
27
|
+
super
|
|
28
|
+
self.typecheck!(:name, Symbol)
|
|
29
|
+
self.typecheck!(:schema, Schema, nullable: true)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Column < Webhookdb::TypedStruct
|
|
34
|
+
include ColumnTypes
|
|
35
|
+
attr_reader :name, :type, :nullable, :unique, :index, :index_where, :pk, :backfill_statement, :backfill_expr
|
|
36
|
+
alias nullable? nullable
|
|
37
|
+
alias unique? unique
|
|
38
|
+
alias index? index
|
|
39
|
+
alias pk? pk
|
|
40
|
+
|
|
41
|
+
def initialize(**kwargs)
|
|
42
|
+
super
|
|
43
|
+
self.typecheck!(:name, Symbol)
|
|
44
|
+
self.typecheck!(:type, Symbol)
|
|
45
|
+
self.typecheck!(:nullable, :boolean)
|
|
46
|
+
self.typecheck!(:unique, :boolean)
|
|
47
|
+
self.typecheck!(:index, :boolean)
|
|
48
|
+
self.typecheck!(:pk, :boolean)
|
|
49
|
+
raise ArgumentError, "type #{self.type.inspect} is not known" unless COLUMN_TYPES.include?(self.type)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _defaults
|
|
53
|
+
return {nullable: true, unique: false, index: false, pk: false}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Index < Webhookdb::TypedStruct
|
|
58
|
+
attr_reader :name, :table, :targets, :unique, :where
|
|
59
|
+
|
|
60
|
+
def initialize(**kwargs)
|
|
61
|
+
super
|
|
62
|
+
self.typecheck!(:name, Symbol)
|
|
63
|
+
self.typecheck!(:table, Table)
|
|
64
|
+
self.typecheck!(:targets, Array)
|
|
65
|
+
self.typecheck!(:unique, :boolean)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _defaults
|
|
69
|
+
return {unique: false}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @!attribute name
|
|
73
|
+
# @return [Symbol]
|
|
74
|
+
# @!attribute table
|
|
75
|
+
# @return [Table]
|
|
76
|
+
# @!attribute targets
|
|
77
|
+
# @return [Array<Column>]
|
|
78
|
+
# @!attribute unique
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class TableDescriptor < Webhookdb::TypedStruct
|
|
83
|
+
attr_reader :table, :columns, :indices
|
|
84
|
+
|
|
85
|
+
def initialize(**kwargs)
|
|
86
|
+
super
|
|
87
|
+
self.typecheck!(:table, Table)
|
|
88
|
+
self.typecheck!(:columns, Array)
|
|
89
|
+
self.typecheck!(:indices, Array)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @!attribute table
|
|
93
|
+
# @return [Table]
|
|
94
|
+
# @!attribute columns
|
|
95
|
+
# @return [Array<Column>]
|
|
96
|
+
# @!attribute indices
|
|
97
|
+
# @return [Array<Index>]
|
|
98
|
+
|
|
99
|
+
def _defaults
|
|
100
|
+
return {indices: []}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Abstract class representing a DB connection.
|
|
105
|
+
# Ususually this is a Sequel connection,
|
|
106
|
+
# but in could just be a stored URL (like for Snowflake
|
|
107
|
+
# we have to call snowsql each time).
|
|
108
|
+
class Connection
|
|
109
|
+
def execute(sql)
|
|
110
|
+
raise NotImplementedError
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class SequelConnection < Connection
|
|
115
|
+
include Webhookdb::Dbutil
|
|
116
|
+
|
|
117
|
+
def initialize(url)
|
|
118
|
+
super()
|
|
119
|
+
@url = url
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def using(**kw, &)
|
|
123
|
+
borrow_conn(@url, **kw, &)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def execute(sql, **kw)
|
|
127
|
+
borrow_conn(@url, **kw) do |db|
|
|
128
|
+
db << sql
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Return a new Connection for the adapter.
|
|
134
|
+
# By default, return a SequelConnection,
|
|
135
|
+
# but adapters not using Sequel will need their own type.
|
|
136
|
+
# @return [Connection]
|
|
137
|
+
def connection(url)
|
|
138
|
+
return SequelConnection.new(url)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @param [Schema] schema
|
|
142
|
+
# @param [Boolean] if_not_exists
|
|
143
|
+
# @return [String]
|
|
144
|
+
def create_schema_sql(schema, if_not_exists: false)
|
|
145
|
+
raise NotImplementedError
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @param [Table] table
|
|
149
|
+
# @param [Array<Column>] columns
|
|
150
|
+
# @param [Schema] schema
|
|
151
|
+
# @param [Boolean] if_not_exists
|
|
152
|
+
# @return [String]
|
|
153
|
+
def create_table_sql(table, columns, schema: nil, if_not_exists: false)
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @param [Index] index
|
|
158
|
+
# @return [String]
|
|
159
|
+
def create_index_sql(index, concurrently:)
|
|
160
|
+
raise NotImplementedError
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @param [Table] table
|
|
164
|
+
# @param [Column] column
|
|
165
|
+
# @param [Boolean] if_not_exists
|
|
166
|
+
# @return [String]
|
|
167
|
+
def add_column_sql(table, column, if_not_exists: false)
|
|
168
|
+
raise NotImplementedError
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Given a table and a (temporary) file with CSV data,
|
|
172
|
+
# import it into the table. Usually this is a COPY INTO command.
|
|
173
|
+
# For PG it would read from stdin,
|
|
174
|
+
# for Snowflake it would have to stage the file.
|
|
175
|
+
# @param [Connection] connection
|
|
176
|
+
# @param [File] file
|
|
177
|
+
# @param [Table] table
|
|
178
|
+
# @param [Column] pk_col Use this to identifier the same row between source and destination.
|
|
179
|
+
# @param [Array<Column>] copy_columns All columns to copy.
|
|
180
|
+
# NOTE: This includes the pk column, since it should be copied, as we depend on it persisting.
|
|
181
|
+
def merge_from_csv(connection, file, table, pk_col, copy_columns)
|
|
182
|
+
raise NotImplementedError
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def verify_connection(url, timeout: 2, statement: "SELECT 1")
|
|
186
|
+
return self._verify_connection(url, timeout:, statement:)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @param [String] url
|
|
190
|
+
# @return [Webhookdb::DBAdapter]
|
|
191
|
+
def self.adapter(url)
|
|
192
|
+
case url
|
|
193
|
+
when /^postgres/
|
|
194
|
+
return Webhookdb::DBAdapter::PG.new
|
|
195
|
+
when /^snowflake/
|
|
196
|
+
return Webhookdb::DBAdapter::Snowflake.new
|
|
197
|
+
else
|
|
198
|
+
raise UnsupportedAdapter, "no adapter available for #{url}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def self.supported_adapters_message
|
|
203
|
+
return "Postgres (postgres://), SnowflakeDB (snowflake://)"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
require "webhookdb/db_adapter/pg"
|
|
208
|
+
require "webhookdb/db_adapter/snowflake"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sequel"
|
|
4
|
+
|
|
5
|
+
# Mixin that provides helpers when dealing with databases and connections.
|
|
6
|
+
#
|
|
7
|
+
# Use borrow_conn to create a connection that is disconnected after the block runs.
|
|
8
|
+
# A block must be given.
|
|
9
|
+
# By default, this connection uses the Webhookdb logger,
|
|
10
|
+
# and uses test: false and keep_reference: false Sequel.connect options,
|
|
11
|
+
# since this is a quick-lived and self-managed connection.
|
|
12
|
+
#
|
|
13
|
+
# Use take_conn where you will take care of disconnecting the connection.
|
|
14
|
+
# Note you MUST take care to call `disconnect` at some point
|
|
15
|
+
# or connections will leak.
|
|
16
|
+
module Webhookdb::Dbutil
|
|
17
|
+
include Appydays::Configurable
|
|
18
|
+
|
|
19
|
+
# See http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html#label-General+connection+options
|
|
20
|
+
# for Sequel option details.
|
|
21
|
+
configurable(:dbutil) do
|
|
22
|
+
# The number of (Float) seconds that should be considered "slow" for a
|
|
23
|
+
# single query; queries that take longer than this amount of time will be logged
|
|
24
|
+
# at `warn` level.
|
|
25
|
+
setting :slow_query_seconds, 0.1
|
|
26
|
+
|
|
27
|
+
# Default this to whatever concurrency is appropriate for the process type.
|
|
28
|
+
# PROC_MODE is set in the initializers in the config dir.
|
|
29
|
+
setting :max_connections,
|
|
30
|
+
(if ENV["PROC_MODE"] == "sidekiq"
|
|
31
|
+
ENV.fetch("SIDEKIQ_CONCURRENCY", "10").to_i
|
|
32
|
+
elsif ENV["PROC_MIDE"] == "puma"
|
|
33
|
+
ENV.fetch("WEB_CONCURRENCY", "4").to_i
|
|
34
|
+
else
|
|
35
|
+
4
|
|
36
|
+
end)
|
|
37
|
+
setting :pool_timeout, 10
|
|
38
|
+
# Set to 'disable' to work around segfault.
|
|
39
|
+
# See https://github.com/ged/ruby-pg/issues/538
|
|
40
|
+
setting :gssencmode, ""
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Needed when we need to work with a source.
|
|
44
|
+
MOCK_CONN = Sequel.connect("mock://")
|
|
45
|
+
|
|
46
|
+
module_function def borrow_conn(url, **opts, &block)
|
|
47
|
+
raise LocalJumpError, "borrow_conn requires a block" if block.nil?
|
|
48
|
+
opts = conn_opts(opts)
|
|
49
|
+
Sequel.connect(url, **opts, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module_function def take_conn(url, **opts, &block)
|
|
53
|
+
raise LocalJumpError, "take_conn cannot use a block" unless block.nil?
|
|
54
|
+
opts = conn_opts(opts)
|
|
55
|
+
return Sequel.connect(url, **opts, &block)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private module_function def conn_opts(opts)
|
|
59
|
+
res = Webhookdb::Dbutil.configured_connection_options
|
|
60
|
+
res.merge!(opts)
|
|
61
|
+
res[:test] = false unless res.key?(:test)
|
|
62
|
+
res[:loggers] = [Webhookdb.logger] unless res.key?(:logger) || res.key?(:loggers)
|
|
63
|
+
res[:keep_reference] = false unless res.key?(:keep_reference)
|
|
64
|
+
return res
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.configured_connection_options
|
|
68
|
+
res = {}
|
|
69
|
+
res[:sql_log_level] ||= :debug
|
|
70
|
+
res[:log_warn_duration] ||= Webhookdb::Dbutil.slow_query_seconds
|
|
71
|
+
res[:max_connections] ||= Webhookdb::Dbutil.max_connections
|
|
72
|
+
res[:pool_timeout] ||= Webhookdb::Dbutil.pool_timeout
|
|
73
|
+
res[:driver_options] = {}
|
|
74
|
+
(res[:driver_options][:gssencmode] = Webhookdb::Dbutil.gssencmode) if Webhookdb::Dbutil.gssencmode.present?
|
|
75
|
+
return res
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module_function def displaysafe_url(url)
|
|
79
|
+
u = URI(url)
|
|
80
|
+
u.user = "***"
|
|
81
|
+
u.password = "***"
|
|
82
|
+
return u.to_s
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
module_function def reduce_expr(dataset, op_symbol, operands, method: :where)
|
|
86
|
+
return dataset if operands.blank?
|
|
87
|
+
present_ops = operands.select(&:present?)
|
|
88
|
+
return dataset if present_ops.empty?
|
|
89
|
+
full_op = present_ops.reduce(&op_symbol)
|
|
90
|
+
return dataset.send(method, full_op)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::DemoMode
|
|
4
|
+
include Appydays::Configurable
|
|
5
|
+
include Appydays::Loggable
|
|
6
|
+
|
|
7
|
+
configurable(:demo_mode) do
|
|
8
|
+
setting :client_enabled, false
|
|
9
|
+
setting :customer_email, "demo@webhookdb.com"
|
|
10
|
+
setting :customer_org_key, "demo_org"
|
|
11
|
+
setting :demo_org_id, 0
|
|
12
|
+
setting :demo_data_api_host, "https://api.webhookdb.com"
|
|
13
|
+
setting :example_datasets_enabled, false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Should requests to this server allow demo mode? Usually this is true when running
|
|
18
|
+
# in a demo context, like initial local development.
|
|
19
|
+
def client_enabled? = self.client_enabled
|
|
20
|
+
# Should requests to this server respond to API requests for demo data?
|
|
21
|
+
def server_enabled? = self.demo_org_id.positive?
|
|
22
|
+
|
|
23
|
+
# @return [Array<Webhookdb::OrganizationMembership, Webhookdb::Replicator::StateMachineStep, String>]
|
|
24
|
+
def handle_auth
|
|
25
|
+
raise Webhookdb::InvalidPrecondition unless self.client_enabled?
|
|
26
|
+
_, customer = Webhookdb::Customer.find_or_create_for_email(self.customer_email)
|
|
27
|
+
membership = self._ensure_membership(customer)
|
|
28
|
+
membership.organization.publish_deferred("syncdemodata", membership.organization_id)
|
|
29
|
+
step = Webhookdb::Replicator::StateMachineStep.new.completed
|
|
30
|
+
message = %(Hi there! This is a demo version of WebhookDB.
|
|
31
|
+
|
|
32
|
+
You have been logged in automatically.
|
|
33
|
+
|
|
34
|
+
Your WebhookDB organization has also been set up with replicators for some APIs, like GitHub.
|
|
35
|
+
|
|
36
|
+
Run `webhookdb db connection` to get your database connection string,
|
|
37
|
+
and see what data is available.
|
|
38
|
+
|
|
39
|
+
To set up a new replicator, run `webhookdb services list` to see what is available.
|
|
40
|
+
|
|
41
|
+
You can also head to `webhookdb.com/deploy-builder` to prepare an environment for a deployment
|
|
42
|
+
into your own environment, like AWS or Heroku.
|
|
43
|
+
|
|
44
|
+
Or check out https://webhookdb.com to sign up for WebhookDB Cloud so this is all managed for you.)
|
|
45
|
+
return membership, step, message
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def _ensure_membership(customer)
|
|
49
|
+
org = Webhookdb::Organization.find_or_create(key: self.customer_org_key) do |o|
|
|
50
|
+
o.name = "Demo Org"
|
|
51
|
+
o.billing_email = customer.email
|
|
52
|
+
end
|
|
53
|
+
mem = customer.all_memberships_dataset[organization: org] || customer.add_membership(
|
|
54
|
+
organization: org, membership_role: Webhookdb::Role.admin_role, verified: true, is_default: true,
|
|
55
|
+
)
|
|
56
|
+
return mem
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_demo_data
|
|
60
|
+
evar = "DEMO_MODE_DEMO_ORG_ID"
|
|
61
|
+
raise Webhookdb::InvalidPrecondition, "#{evar} not set" unless self.server_enabled?
|
|
62
|
+
org = Webhookdb::Organization[self.demo_org_id] or
|
|
63
|
+
raise Webhookdb::InvalidPrecondition, "#{evar} #{self.demo_org_id} does not exist"
|
|
64
|
+
demo_sints = org.service_integrations.select { |sint| sint.service_name.start_with?("github_") }
|
|
65
|
+
data = demo_sints.map do |sint|
|
|
66
|
+
rows_data = sint.replicator.readonly_dataset { |ds| ds.select_map(:data) }
|
|
67
|
+
{
|
|
68
|
+
service_name: sint.service_name,
|
|
69
|
+
rows_data:,
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
return {data:}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def sync_demo_data(org)
|
|
76
|
+
can_run = Webhookdb::DemoMode.client_enabled? ||
|
|
77
|
+
Webhookdb::DemoMode.example_datasets_enabled
|
|
78
|
+
return false unless can_run
|
|
79
|
+
resp = Webhookdb::Http.post("#{self.demo_data_api_host}/v1/demo/data", timeout: nil, logger: self.logger)
|
|
80
|
+
# First, create/migrate all service integrations from the demo server.
|
|
81
|
+
sints_and_datas = []
|
|
82
|
+
resp.parsed_response["data"].each do |h|
|
|
83
|
+
service_name = h.fetch("service_name")
|
|
84
|
+
table_name = "#{service_name}_demo"
|
|
85
|
+
sint = org.service_integrations.find { |si| si.table_name == table_name } ||
|
|
86
|
+
org.add_service_integration(service_name:, table_name:)
|
|
87
|
+
sints_and_datas << [sint, h.fetch("rows_data")]
|
|
88
|
+
end
|
|
89
|
+
org.migrate_replication_tables
|
|
90
|
+
# Now populate them with data.
|
|
91
|
+
sints_and_datas.each do |(sint, rows)|
|
|
92
|
+
repl = sint.replicator
|
|
93
|
+
rows.each do |row|
|
|
94
|
+
repl.upsert_webhook_body(row)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
return true
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "webhookdb/slack"
|
|
5
|
+
|
|
6
|
+
# Decouples the need to alert from the way we want to handle alerts.
|
|
7
|
+
# This is for something in between purely technical alerts (error handling in Sentry)
|
|
8
|
+
# and ops/marketing alerts (which may post to Slack or whatever).
|
|
9
|
+
# Instead of having to rewrite many async jobs to not use Slack,
|
|
10
|
+
# we can just modiy the one job that handles developer alerts.
|
|
11
|
+
class Webhookdb::DeveloperAlert
|
|
12
|
+
include Appydays::Configurable
|
|
13
|
+
|
|
14
|
+
attr_accessor :subsystem, :emoji, :fallback, :fields
|
|
15
|
+
|
|
16
|
+
def initialize(subsystem:, emoji:, fallback:, fields:)
|
|
17
|
+
@subsystem = subsystem
|
|
18
|
+
@emoji = emoji
|
|
19
|
+
@fallback = fallback
|
|
20
|
+
@fields = fields
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def as_json
|
|
24
|
+
return {
|
|
25
|
+
subsystem:,
|
|
26
|
+
emoji:,
|
|
27
|
+
fallback:,
|
|
28
|
+
fields:,
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def emit
|
|
33
|
+
Amigo.publish("webhookdb.developeralert.emitted", self.as_json)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handle
|
|
37
|
+
notifier = Webhookdb::Slack.new_notifier(
|
|
38
|
+
channel: "#webhookdb-notifications",
|
|
39
|
+
username: @subsystem,
|
|
40
|
+
icon_emoji: @emoji,
|
|
41
|
+
)
|
|
42
|
+
notifier.post(
|
|
43
|
+
attachments: [
|
|
44
|
+
{
|
|
45
|
+
fallback: @fallback,
|
|
46
|
+
fields: @fields,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|