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,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/jobs/webhook_subscription_delivery_attempt"
|
|
4
|
+
|
|
5
|
+
# Webhook subscriptions have a few parts:
|
|
6
|
+
#
|
|
7
|
+
# - The WebhookSubscription itself (this model),
|
|
8
|
+
# which represents a user's desire to receive all webhooks at a URL.
|
|
9
|
+
# - The individual Delivery, which is a single 'rowupsert' event being
|
|
10
|
+
# delivered to a subscription.
|
|
11
|
+
# That is, if multiple rowupserts are done, there will be multiple Deliveries
|
|
12
|
+
# to a single Subscription, one for each rowupsert.
|
|
13
|
+
# Likewise, if a single rowupsert is done, but there are multiple Subscriptions,
|
|
14
|
+
# there will be multiple Deliveries, one to each Subscription.
|
|
15
|
+
# - Async job that listens for rowupsert events and enqueues new deliveries.
|
|
16
|
+
# - When a delivery is 'enqueued', it is created in the database,
|
|
17
|
+
# and then a sidekiq job is put into Redis.
|
|
18
|
+
# This sidekiq job operates OUTSIDE of our normal job system
|
|
19
|
+
# since we do not want to bother with audit logging or routing
|
|
20
|
+
# (enough history is in the DB already, though we could add it if needed).
|
|
21
|
+
# - We attempt the delivery until it succeeds, or we run out of attempts.
|
|
22
|
+
# See #attempt_delivery.
|
|
23
|
+
#
|
|
24
|
+
class Webhookdb::WebhookSubscription < Webhookdb::Postgres::Model(:webhook_subscriptions)
|
|
25
|
+
plugin :timestamps
|
|
26
|
+
plugin :column_encryption do |enc|
|
|
27
|
+
enc.column :webhook_secret
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
many_to_one :service_integration, class: Webhookdb::ServiceIntegration
|
|
31
|
+
many_to_one :organization, class: Webhookdb::Organization
|
|
32
|
+
many_to_one :created_by, class: Webhookdb::Customer
|
|
33
|
+
|
|
34
|
+
# Amount of time we wait for a response from the server.
|
|
35
|
+
TIMEOUT = 10.seconds
|
|
36
|
+
# An individual will be delivered this many times before giving up.
|
|
37
|
+
MAX_DELIVERY_ATTEMPTS = 25
|
|
38
|
+
|
|
39
|
+
dataset_module do
|
|
40
|
+
def active
|
|
41
|
+
return self.where(deactivated_at: nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_notify
|
|
45
|
+
return self.active
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def active?
|
|
50
|
+
return !self.deactivated?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deactivated?
|
|
54
|
+
return !!self.deactivated_at
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def deactivate(at: Time.now)
|
|
58
|
+
self.deactivated_at = at
|
|
59
|
+
return self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fetch_organization
|
|
63
|
+
return self.organization || self.service_integration.organization
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def status
|
|
67
|
+
return self.deactivated? ? "deactivated" : "active"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Deliver the webhook payload to the configured URL.
|
|
71
|
+
# This does NOT create or deal with WebhookSubscription::Delivery;
|
|
72
|
+
# it is for the actual delivering.
|
|
73
|
+
def deliver(service_name:, table_name:, row:, external_id:, external_id_column:, headers: {})
|
|
74
|
+
body = {
|
|
75
|
+
service_name:,
|
|
76
|
+
table_name:,
|
|
77
|
+
row:,
|
|
78
|
+
external_id:,
|
|
79
|
+
external_id_column:,
|
|
80
|
+
}
|
|
81
|
+
return Webhookdb::Http.post(
|
|
82
|
+
self.deliver_to_url,
|
|
83
|
+
body,
|
|
84
|
+
headers: {"Whdb-Webhook-Secret" => self.webhook_secret}.merge(headers),
|
|
85
|
+
timeout: TIMEOUT,
|
|
86
|
+
logger: self.logger,
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def deliver_test_event(external_id: SecureRandom.hex(6))
|
|
91
|
+
return self.deliver(
|
|
92
|
+
service_name: "test service",
|
|
93
|
+
table_name: "test_table_name",
|
|
94
|
+
external_id:,
|
|
95
|
+
external_id_column: "external_id",
|
|
96
|
+
row: {data: ["alpha", "beta", "charlie", "delta"]},
|
|
97
|
+
headers: {"Whdb-Test-Event" => "1"},
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def create_delivery(payload)
|
|
102
|
+
return Webhookdb::WebhookSubscription::Delivery.create(webhook_subscription: self, payload:)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Create a new Delivery and enqueue it for async processing.
|
|
106
|
+
def enqueue_delivery(payload)
|
|
107
|
+
delivery = self.create_delivery(payload)
|
|
108
|
+
Webhookdb::Jobs::WebhookSubscriptionDeliveryEvent.perform_async(delivery.id)
|
|
109
|
+
return delivery
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Attempt to deliver the payload in +d+ to the configured URL (see #deliver).
|
|
113
|
+
# Noops if the subscription is deactivated.
|
|
114
|
+
#
|
|
115
|
+
# If the attempt succeeds, no attempts are enqueued.
|
|
116
|
+
#
|
|
117
|
+
# If the attempt fails, another async job to reattempt delivery
|
|
118
|
+
# will be enqueued for some time in the future based on the number of attempts.
|
|
119
|
+
# The timestamp and http status are stored on the delivery for future analysis.
|
|
120
|
+
#
|
|
121
|
+
# After too many failures, no more attempts will be enqueued.
|
|
122
|
+
# Instead, a developer alert is emitted.
|
|
123
|
+
#
|
|
124
|
+
# In the future, we will support manually re-attempting delivery (success of which should
|
|
125
|
+
# clear deactivated subscriptions), and automatic deactivation
|
|
126
|
+
# (after some criteria of abandonment has been met).
|
|
127
|
+
def attempt_delivery(d)
|
|
128
|
+
return if self.deactivated?
|
|
129
|
+
d.db.transaction do
|
|
130
|
+
d.lock!
|
|
131
|
+
attempt = d.attempt_count + 1
|
|
132
|
+
begin
|
|
133
|
+
r = self.deliver(**d.payload.symbolize_keys, headers: {"Whdb-Attempt" => attempt.to_s})
|
|
134
|
+
d.add_attempt(status: r.code)
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
self.logger.error(
|
|
137
|
+
"webhook_subscription_delivery_failure",
|
|
138
|
+
error: e,
|
|
139
|
+
webhook_subscription_id: self.id,
|
|
140
|
+
webhook_subscription_delivery_id: d.id,
|
|
141
|
+
)
|
|
142
|
+
d.add_attempt(status: e.is_a?(Webhookdb::Http::Error) ? e.status : 0)
|
|
143
|
+
if attempt < MAX_DELIVERY_ATTEMPTS
|
|
144
|
+
self._retry(d, attempt)
|
|
145
|
+
else
|
|
146
|
+
self._fatal(d, e)
|
|
147
|
+
end
|
|
148
|
+
ensure
|
|
149
|
+
d.save_changes
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def _retry(delivery, attempt)
|
|
155
|
+
delay = self.class.backoff_for_attempt(attempt)
|
|
156
|
+
Webhookdb::Jobs::WebhookSubscriptionDeliveryEvent.perform_in(delay, delivery.id)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def self.backoff_for_attempt(attempt)
|
|
160
|
+
return 1 if attempt <= 1
|
|
161
|
+
return attempt * 2 if attempt <= 10
|
|
162
|
+
return attempt * 3 if attempt <= 20
|
|
163
|
+
return attempt * 4
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def _fatal(d, e)
|
|
167
|
+
Webhookdb::DeveloperAlert.new(
|
|
168
|
+
subsystem: "Webhook Subscriptions",
|
|
169
|
+
emoji: ":hook:",
|
|
170
|
+
fallback: "Error delivering WebhookSubscription::Delivery[id: #{d.id}, subscription_id: #{self.id}]: #{e}",
|
|
171
|
+
fields: [
|
|
172
|
+
{title: "Org", value: self.fetch_organization.display_string, short: true},
|
|
173
|
+
{title: "Creator", value: self.created_by&.email, short: true},
|
|
174
|
+
{title: "Delivery", value: "#{d.id}, Subscription: #{self.id}, Attempts: #{d.attempt_count}"},
|
|
175
|
+
{title: "URL", value: self.deliver_to_url, short: false},
|
|
176
|
+
{title: "Exception", value: e.inspect, short: false},
|
|
177
|
+
],
|
|
178
|
+
).emit
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def associated_type
|
|
182
|
+
return "organization" unless self.organization_id.nil?
|
|
183
|
+
return "service_integration" unless self.service_integration_id.nil?
|
|
184
|
+
return ""
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def associated_id
|
|
188
|
+
return self.organization.key unless self.organization_id.nil?
|
|
189
|
+
return self.service_integration.opaque_id unless self.service_integration_id.nil?
|
|
190
|
+
return ""
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#
|
|
194
|
+
# :Sequel Hooks:
|
|
195
|
+
#
|
|
196
|
+
|
|
197
|
+
def before_create
|
|
198
|
+
self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("wsb")
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Table: webhook_subscriptions
|
|
203
|
+
# ----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
204
|
+
# Columns:
|
|
205
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
206
|
+
# deliver_to_url | text | NOT NULL
|
|
207
|
+
# webhook_secret | text | NOT NULL
|
|
208
|
+
# opaque_id | text | NOT NULL
|
|
209
|
+
# service_integration_id | integer |
|
|
210
|
+
# organization_id | integer |
|
|
211
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
212
|
+
# updated_at | timestamp with time zone |
|
|
213
|
+
# created_by_id | integer |
|
|
214
|
+
# deactivated_at | timestamp with time zone |
|
|
215
|
+
# Indexes:
|
|
216
|
+
# webhook_subscriptions_pkey | PRIMARY KEY btree (id)
|
|
217
|
+
# webhook_subscriptions_opaque_id_key | UNIQUE btree (opaque_id)
|
|
218
|
+
# Check constraints:
|
|
219
|
+
# service_integration_or_org | (service_integration_id IS NULL AND organization_id IS NOT NULL OR service_integration_id IS NOT NULL AND organization_id IS NULL)
|
|
220
|
+
# Foreign key constraints:
|
|
221
|
+
# webhook_subscriptions_created_by_id_fkey | (created_by_id) REFERENCES customers(id)
|
|
222
|
+
# webhook_subscriptions_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
|
223
|
+
# webhook_subscriptions_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id)
|
|
224
|
+
# Referenced By:
|
|
225
|
+
# webhook_subscription_deliveries | webhook_subscription_deliveries_webhook_subscription_id_fkey | (webhook_subscription_id) REFERENCES webhook_subscriptions(id)
|
|
226
|
+
# ----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Map Windows Timezone names to IANA names (and ActiveSupport timezones).
|
|
4
|
+
# Outlook calendar uses these timezones.
|
|
5
|
+
module Webhookdb::WindowsTZ
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :_win_to_tz
|
|
8
|
+
|
|
9
|
+
# @return [Hash<String => ActiveSupport::TimeZone>]
|
|
10
|
+
def windows_name_to_tz
|
|
11
|
+
return self._win_to_tz if self._win_to_tz
|
|
12
|
+
win_to_tz = {}
|
|
13
|
+
self._win_to_tz = win_to_tz
|
|
14
|
+
|
|
15
|
+
all_win_names = Set.new
|
|
16
|
+
File.open(Webhookdb::DATA_DIR + "windows_tz.txt").each do |line|
|
|
17
|
+
line.strip!
|
|
18
|
+
next if line.blank? || line.start_with?("#")
|
|
19
|
+
iana, win = line.split(/\s/, 2)
|
|
20
|
+
next if win_to_tz.include?(win)
|
|
21
|
+
all_win_names.add(win)
|
|
22
|
+
tz = ActiveSupport::TimeZone[iana]
|
|
23
|
+
next if tz.nil?
|
|
24
|
+
win_to_tz[win] = tz
|
|
25
|
+
end
|
|
26
|
+
win_to_tz.each_key { |k| all_win_names.delete(k) }
|
|
27
|
+
raise Webhookdb::InvariantViolation, "unmapped windows timezones: #{all_win_names.join(', ')}" unless
|
|
28
|
+
all_win_names.empty?
|
|
29
|
+
return win_to_tz
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Webhookdb::Xml
|
|
4
|
+
module Atom
|
|
5
|
+
def self.parse(thing)
|
|
6
|
+
Parser.new(thing).to_hash
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.parse_entry(thing)
|
|
10
|
+
p = Parser.new(thing)
|
|
11
|
+
p.parse_entry(p.doc.root)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Parser
|
|
15
|
+
attr_reader :doc
|
|
16
|
+
|
|
17
|
+
def initialize(thing)
|
|
18
|
+
@doc = Nokogiri::XML.parse(thing, &:noblanks)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_hash
|
|
22
|
+
entries = []
|
|
23
|
+
feed = {"entries" => entries}
|
|
24
|
+
@doc.root.children.each do |c|
|
|
25
|
+
if c.is_a?(Nokogiri::XML::Text)
|
|
26
|
+
next
|
|
27
|
+
elsif c.name == "entry"
|
|
28
|
+
entries << self.parse_entry(c)
|
|
29
|
+
elsif self.spec_attr?(c)
|
|
30
|
+
feed[self.fqn(c)] = self.parse_spec_attr(c)
|
|
31
|
+
elsif self.simple_text?(c)
|
|
32
|
+
feed[self.fqn(c)] = self.text(c)
|
|
33
|
+
else
|
|
34
|
+
feed[self.fqn(c)] = self.parse_to_hash(c)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
return feed
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
protected def simple_text?(c) = c.children.size == 1 && c.children[0].is_a?(Nokogiri::XML::Text)
|
|
41
|
+
protected def spec_attr?(c) = ["link", "category"].include?(c.name)
|
|
42
|
+
|
|
43
|
+
protected def parse_spec_attr(c)
|
|
44
|
+
h = {}
|
|
45
|
+
c.attributes.each do |k, v|
|
|
46
|
+
h[k] = v.value
|
|
47
|
+
end
|
|
48
|
+
h["text"] = self.text(c) if simple_text?(c)
|
|
49
|
+
return h
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parse_entry(e)
|
|
53
|
+
h = {}
|
|
54
|
+
e.children.each do |c|
|
|
55
|
+
if c.name == "content"
|
|
56
|
+
content = {}
|
|
57
|
+
h["content"] = content
|
|
58
|
+
content["value"] = c.children.to_s if c.children.to_s.present?
|
|
59
|
+
c.attributes.each do |k, v|
|
|
60
|
+
content[k] = v.value
|
|
61
|
+
end
|
|
62
|
+
elsif self.spec_attr?(c)
|
|
63
|
+
h[self.fqn(c)] = self.parse_spec_attr(c)
|
|
64
|
+
else
|
|
65
|
+
h[self.fqn(c)] = c.text
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
return h
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected def fqn(c)
|
|
72
|
+
return c.name unless c.namespace&.prefix
|
|
73
|
+
return "#{c.namespace.prefix}:#{c.name}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
protected def parse_to_hash(c)
|
|
77
|
+
h = {}
|
|
78
|
+
c.children.each do |cc|
|
|
79
|
+
h[self.fqn(cc)] = self.text(cc)
|
|
80
|
+
end
|
|
81
|
+
return h
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected def text(c)
|
|
85
|
+
t = c.children.first
|
|
86
|
+
return "" if t.nil?
|
|
87
|
+
raise ArgumentError, "child is not text: #{c}" unless t.is_a?(Nokogiri::XML::Text)
|
|
88
|
+
return t.text
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/webhookdb.rb
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/core_ext"
|
|
5
|
+
require "appydays/configurable"
|
|
6
|
+
require "appydays/loggable"
|
|
7
|
+
require "money"
|
|
8
|
+
require "pathname"
|
|
9
|
+
require "phony"
|
|
10
|
+
|
|
11
|
+
require "webhookdb/json"
|
|
12
|
+
|
|
13
|
+
if (heroku_app = ENV.fetch("MERGE_HEROKU_ENV", nil))
|
|
14
|
+
text = `heroku config -j --app=#{heroku_app}`
|
|
15
|
+
json = Oj.load(text)
|
|
16
|
+
json.each do |k, v|
|
|
17
|
+
ENV[k] = v
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Money.locale_backend = :i18n
|
|
22
|
+
Money.default_currency = "USD"
|
|
23
|
+
Money.rounding_mode = BigDecimal::ROUND_HALF_UP
|
|
24
|
+
|
|
25
|
+
module Webhookdb
|
|
26
|
+
include Appydays::Loggable
|
|
27
|
+
include Appydays::Configurable
|
|
28
|
+
|
|
29
|
+
# Error raised when we cannot take an action
|
|
30
|
+
# because some condition has not been set up right.
|
|
31
|
+
class InvalidPrecondition < StandardError; end
|
|
32
|
+
|
|
33
|
+
# Error raised when, after we take an action,
|
|
34
|
+
# something we expect to have changed has not changed.
|
|
35
|
+
class InvalidPostcondition < StandardError; end
|
|
36
|
+
|
|
37
|
+
# Some invariant has been violated, which we never expect to see.
|
|
38
|
+
class InvariantViolation < StandardError; end
|
|
39
|
+
|
|
40
|
+
# Error raised when a customer gives us some invalid input.
|
|
41
|
+
# Allows the library to raise the error with the message,
|
|
42
|
+
# and is caught automatically by the service as a 400.
|
|
43
|
+
class InvalidInput < StandardError; end
|
|
44
|
+
|
|
45
|
+
# Raised when an organization's database cannot be modified.
|
|
46
|
+
class DatabaseLocked < StandardError; end
|
|
47
|
+
|
|
48
|
+
# Used in various places that need to short-circuit code in regression mode.
|
|
49
|
+
class RegressionModeSkip < StandardError; end
|
|
50
|
+
|
|
51
|
+
APPLICATION_NAME = "Webhookdb"
|
|
52
|
+
RACK_ENV = ENV.fetch("RACK_ENV", "development")
|
|
53
|
+
COMMIT = ENV.fetch("HEROKU_SLUG_COMMIT", "unknown-commit")
|
|
54
|
+
RELEASE = ENV.fetch("HEROKU_RELEASE_VERSION", "unknown-release")
|
|
55
|
+
RELEASE_CREATED_AT = ENV.fetch("HEROKU_RELEASE_CREATED_AT") { Time.at(0).utc.iso8601 }
|
|
56
|
+
INTEGRATION_TESTS_ENABLED = ENV.fetch("INTEGRATION_TESTS", false)
|
|
57
|
+
|
|
58
|
+
DATA_DIR = Pathname(__FILE__).dirname.parent + "data"
|
|
59
|
+
|
|
60
|
+
configurable(:webhookdb) do
|
|
61
|
+
setting :log_level_override,
|
|
62
|
+
nil,
|
|
63
|
+
key: "LOG_LEVEL",
|
|
64
|
+
side_effect: ->(v) { Appydays::Loggable.default_level = v if v }
|
|
65
|
+
setting :log_format, nil
|
|
66
|
+
setting :app_url, "http://localhost:18002"
|
|
67
|
+
setting :api_url, "http://localhost:#{ENV.fetch('PORT', 18_001)}"
|
|
68
|
+
setting :bust_idempotency, false
|
|
69
|
+
setting :http_user_agent, ""
|
|
70
|
+
setting :oss_repo_url, "https://github.com/webhookdb/webhookdb"
|
|
71
|
+
setting :support_email, "hello@webhookdb.com"
|
|
72
|
+
setting :use_globals_cache, false
|
|
73
|
+
setting :regression_mode, false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Regression mode is true when we re replaying webhooks locally,
|
|
77
|
+
# or for some other reason, want to disable certain checks we use in production.
|
|
78
|
+
# For example, we may want to ignore certain errors (like if integrations are missing dependency rows),
|
|
79
|
+
# or disable certain validations (like always assume the webhook is valid).
|
|
80
|
+
def self.regression_mode?
|
|
81
|
+
return self.regression_mode
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
require "webhookdb/method_utilities"
|
|
85
|
+
extend Webhookdb::MethodUtilities
|
|
86
|
+
|
|
87
|
+
require "webhookdb/sentry"
|
|
88
|
+
|
|
89
|
+
def self.load_app
|
|
90
|
+
$stdout.sync = true
|
|
91
|
+
$stderr.sync = true
|
|
92
|
+
|
|
93
|
+
Appydays::Loggable.configure_12factor(format: self.log_format, application: APPLICATION_NAME)
|
|
94
|
+
|
|
95
|
+
require "webhookdb/postgres"
|
|
96
|
+
Webhookdb::Postgres.load_models
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# :section: Globals cache
|
|
101
|
+
#
|
|
102
|
+
|
|
103
|
+
singleton_attr_reader :globals_cache
|
|
104
|
+
@globals_cache = {}
|
|
105
|
+
|
|
106
|
+
# If globals caching is enabled, see if there is a cached value under +key+
|
|
107
|
+
# and return it if so. If there is not, evaluate the given block and store that value.
|
|
108
|
+
# Generally used for looking up well-known database objects like certain roles.
|
|
109
|
+
def self.cached_get(key)
|
|
110
|
+
if self.use_globals_cache
|
|
111
|
+
result = self.globals_cache[key]
|
|
112
|
+
return result if result
|
|
113
|
+
end
|
|
114
|
+
result = yield()
|
|
115
|
+
self.globals_cache[key] = result
|
|
116
|
+
return result
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# :section: Errors
|
|
121
|
+
#
|
|
122
|
+
|
|
123
|
+
class LockFailed < StandardError; end
|
|
124
|
+
|
|
125
|
+
### Generate a key for the specified Sequel model +instance+ and
|
|
126
|
+
### any additional +parts+ that can be used for idempotent requests.
|
|
127
|
+
def self.idempotency_key(instance, *parts)
|
|
128
|
+
key = "%s-%s" % [instance.class.implicit_table_name, instance.pk]
|
|
129
|
+
|
|
130
|
+
if instance.respond_to?(:updated_at) && instance.updated_at
|
|
131
|
+
parts << instance.updated_at
|
|
132
|
+
elsif instance.respond_to?(:created_at) && instance.created_at
|
|
133
|
+
parts << instance.created_at
|
|
134
|
+
end
|
|
135
|
+
parts << SecureRandom.hex(8) if self.bust_idempotency
|
|
136
|
+
key << "-" << parts.map(&:to_s).join("-") unless parts.empty?
|
|
137
|
+
|
|
138
|
+
return key
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
#
|
|
142
|
+
# :section: Unambiguous/promo code chars
|
|
143
|
+
#
|
|
144
|
+
|
|
145
|
+
# Remove ambiguous characters (L, I, 1 or 0, O) and vowels from possible codes
|
|
146
|
+
# to avoid creating ambiguous codes or real words.
|
|
147
|
+
UNAMBIGUOUS_CHARS = "CDFGHJKMNPQRTVWXYZ23469".chars.freeze
|
|
148
|
+
|
|
149
|
+
def self.take_unambiguous_chars(n)
|
|
150
|
+
return Array.new(n) { UNAMBIGUOUS_CHARS.sample }.join
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Convert a string into something we consistently use for slugs:
|
|
154
|
+
# a-z, 0-9, and underscores only. Leading numbers are converted to words.
|
|
155
|
+
#
|
|
156
|
+
# Acme + Corporation -> "acme_corporation"
|
|
157
|
+
# 1Byte -> "one_byte"
|
|
158
|
+
# 10Byte -> "one0_byte"
|
|
159
|
+
def self.to_slug(s)
|
|
160
|
+
raise ArgumentError, "s cannot be nil" if s.nil?
|
|
161
|
+
return "" if s.blank?
|
|
162
|
+
slug = s.downcase.strip.gsub(/[^a-z0-9]/, "_").squeeze("_")
|
|
163
|
+
slug = NUMBERS_TO_WORDS[slug.first] + slug[1..] if slug.first.match?(/[0-9]/)
|
|
164
|
+
return slug
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
NUMBERS_TO_WORDS = {
|
|
168
|
+
"0" => "zero",
|
|
169
|
+
"1" => "one",
|
|
170
|
+
"2" => "two",
|
|
171
|
+
"3" => "three",
|
|
172
|
+
"4" => "four",
|
|
173
|
+
"5" => "five",
|
|
174
|
+
"6" => "six",
|
|
175
|
+
"7" => "seven",
|
|
176
|
+
"8" => "eight",
|
|
177
|
+
"9" => "nine",
|
|
178
|
+
}.freeze
|
|
179
|
+
|
|
180
|
+
# Return the request user and admin stored in TLS. See service.rb for implementation.
|
|
181
|
+
#
|
|
182
|
+
# Note that the second return value (the admin) will be nil if not authed as an admin,
|
|
183
|
+
# and if an admin is impersonating, the impersonated customer is the first value.
|
|
184
|
+
#
|
|
185
|
+
# Both values will be nil if no user is authed or this is called outside of a request.
|
|
186
|
+
#
|
|
187
|
+
# Usually these fields should only be used where it would be sufficiently difficult
|
|
188
|
+
# to pass the current user through the stack.
|
|
189
|
+
# In the API, you should instead use the 'current customer' methods
|
|
190
|
+
# like current_customer, and admin_customer, NOT using TLS.
|
|
191
|
+
# Outside of the API, this should only be used for things like auditing;
|
|
192
|
+
# it should NOT, for example, ever be used to determine the 'customer owner' of objects
|
|
193
|
+
# being created. Nearly all code will be simpler if the current customer
|
|
194
|
+
# is passed around. But it would be too complex for some code (like auditing)
|
|
195
|
+
# so this system exists. Overuse of request_user_and_admin will inevitably lead to regret.
|
|
196
|
+
def self.request_user_and_admin
|
|
197
|
+
return Thread.current[:request_user], Thread.current[:request_admin]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Return the request user stored in TLS. See service.rb for details.
|
|
201
|
+
def self.set_request_user_and_admin(user, admin, &block)
|
|
202
|
+
if !user.nil? && !admin.nil? && self.request_user_and_admin != [nil, nil]
|
|
203
|
+
raise Webhookdb::InvalidPrecondition, "request user is already set: #{user}, #{admin}"
|
|
204
|
+
end
|
|
205
|
+
Thread.current[:request_user] = user
|
|
206
|
+
Thread.current[:request_admin] = admin
|
|
207
|
+
return if block.nil?
|
|
208
|
+
begin
|
|
209
|
+
yield
|
|
210
|
+
ensure
|
|
211
|
+
Thread.current[:request_user] = nil
|
|
212
|
+
Thread.current[:request_admin] = nil
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
require "webhookdb/aggregate_result"
|
|
218
|
+
require "webhookdb/dbutil"
|
|
219
|
+
require "webhookdb/developer_alert"
|
|
220
|
+
require "webhookdb/http"
|
|
221
|
+
require "webhookdb/phone_number"
|
|
222
|
+
require "webhookdb/replicator"
|
|
223
|
+
require "webhookdb/typed_struct"
|
|
224
|
+
require "webhookdb/webhook_response"
|
data/lib/webterm/apps.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Webterm
|
|
6
|
+
include Appydays::Configurable
|
|
7
|
+
|
|
8
|
+
configurable(:webterm) do
|
|
9
|
+
setting :enforce_ssl, true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
STATIC = File.expand_path("#{File.dirname(__FILE__)}/static")
|
|
13
|
+
|
|
14
|
+
Files = Rack::Files.new(STATIC)
|
|
15
|
+
|
|
16
|
+
class RedirectIndexHtmlToRoot
|
|
17
|
+
REDIRECTS = ["", "/index.html"].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(app)
|
|
20
|
+
@app = app
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
return [302, {"Location" => "/terminal/"}, []] if REDIRECTS.include?(env[Rack::PATH_INFO])
|
|
25
|
+
return @app.call(env)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class ServeIndexHtmlFromRoot
|
|
30
|
+
def initialize(app)
|
|
31
|
+
@app = app
|
|
32
|
+
@cached_html = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def call(env)
|
|
36
|
+
return @app.call(env) unless env[Rack::PATH_INFO] == "/"
|
|
37
|
+
if @cached_html.nil?
|
|
38
|
+
html = File.read(File.join(STATIC, "index.html"))
|
|
39
|
+
html.sub!("/* REPLACE_WHDB_ENV */", "window.whdbEnv = [['WEBHOOKDB_API_HOST', '#{Webhookdb.api_url}']]")
|
|
40
|
+
@cached_html = html.chars
|
|
41
|
+
end
|
|
42
|
+
return [200, {"Content-Type" => "text/html"}, @cached_html]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|