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,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
|