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,506 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/postgres/model"
|
4
|
+
require "appydays/configurable"
|
5
|
+
require "stripe"
|
6
|
+
require "webhookdb/stripe"
|
7
|
+
require "webhookdb/jobs/replication_migration"
|
8
|
+
|
9
|
+
class Webhookdb::Organization < Webhookdb::Postgres::Model(:organizations)
|
10
|
+
class SchemaMigrationError < StandardError; end
|
11
|
+
|
12
|
+
plugin :timestamps
|
13
|
+
plugin :soft_deletes
|
14
|
+
plugin :column_encryption do |enc|
|
15
|
+
enc.column :readonly_connection_url_raw
|
16
|
+
enc.column :admin_connection_url_raw
|
17
|
+
end
|
18
|
+
|
19
|
+
configurable(:organization) do
|
20
|
+
setting :max_query_rows, 1000
|
21
|
+
setting :database_migration_page_size, 1000
|
22
|
+
end
|
23
|
+
|
24
|
+
one_to_one :subscription, class: "Webhookdb::Subscription", key: :stripe_customer_id, primary_key: :stripe_customer_id
|
25
|
+
one_to_many :all_memberships, class: "Webhookdb::OrganizationMembership", order: :id
|
26
|
+
one_to_many :verified_memberships,
|
27
|
+
class: "Webhookdb::OrganizationMembership",
|
28
|
+
conditions: {verified: true},
|
29
|
+
adder: ->(om) { om.update(organization_id: id, verified: true) },
|
30
|
+
order: :id
|
31
|
+
one_to_many :invited_memberships,
|
32
|
+
class: "Webhookdb::OrganizationMembership",
|
33
|
+
conditions: {verified: false},
|
34
|
+
adder: ->(om) { om.update(organization_id: id, verified: false) },
|
35
|
+
order: :id
|
36
|
+
one_to_many :service_integrations, class: "Webhookdb::ServiceIntegration", order: :id
|
37
|
+
one_to_many :webhook_subscriptions, class: "Webhookdb::WebhookSubscription", order: :id
|
38
|
+
many_to_many :feature_roles, class: "Webhookdb::Role", join_table: :feature_roles_organizations, right_key: :role_id
|
39
|
+
one_to_many :all_webhook_subscriptions,
|
40
|
+
class: "Webhookdb::WebhookSubscription",
|
41
|
+
readonly: true,
|
42
|
+
dataset: (lambda do |r|
|
43
|
+
org_sints = Webhookdb::ServiceIntegration.where(organization_id: id)
|
44
|
+
r.associated_dataset.where(
|
45
|
+
Sequel[organization_id: id] |
|
46
|
+
Sequel[service_integration_id: org_sints.select(:id)],
|
47
|
+
)
|
48
|
+
end),
|
49
|
+
eager_loader: (lambda do |eo|
|
50
|
+
org_ids = eo[:id_map].keys
|
51
|
+
all_subs = Webhookdb::WebhookSubscription.
|
52
|
+
left_join(:service_integrations, {id: :service_integration_id}).
|
53
|
+
select(
|
54
|
+
Sequel[:webhook_subscriptions][Sequel.lit("*")],
|
55
|
+
Sequel[:service_integrations][:organization_id].as(:_sint_org_id),
|
56
|
+
).where(
|
57
|
+
Sequel[Sequel[:webhook_subscriptions][:organization_id] => org_ids] |
|
58
|
+
Sequel[Sequel[:service_integrations][:organization_id] => org_ids],
|
59
|
+
).all
|
60
|
+
all_subs_by_org = all_subs.group_by { |sub| sub[:organization_id] || sub[:_sint_org_id] }
|
61
|
+
eo[:rows].each do |org|
|
62
|
+
org.associations[:all_webhook_subscriptions] = all_subs_by_org.fetch(org.id, [])
|
63
|
+
end
|
64
|
+
end)
|
65
|
+
many_through_many :all_sync_targets,
|
66
|
+
[
|
67
|
+
[:service_integrations, :organization_id, :id],
|
68
|
+
],
|
69
|
+
class: "Webhookdb::SyncTarget",
|
70
|
+
left_primary_key: :id,
|
71
|
+
right_primary_key: :service_integration_id,
|
72
|
+
read_only: true,
|
73
|
+
order: [:created_at, :id]
|
74
|
+
one_to_many :database_migrations, class: "Webhookdb::Organization::DatabaseMigration", order: Sequel.desc(:created_at)
|
75
|
+
|
76
|
+
dataset_module do
|
77
|
+
# Return orgs with the given id (if identifier is an integer), or key or name.
|
78
|
+
def with_identifier(identifier)
|
79
|
+
return self.where(id: identifier.to_i) if /^\d+$/.match?(identifier)
|
80
|
+
ds = self.where(Sequel[key: identifier] | Sequel[name: identifier])
|
81
|
+
return ds
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_tags
|
86
|
+
return {
|
87
|
+
organization_id: self.id,
|
88
|
+
organization_key: self.key,
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def before_validation
|
93
|
+
self.minimum_sync_seconds ||= Webhookdb::SyncTarget.default_min_period_seconds
|
94
|
+
self.key ||= Webhookdb.to_slug(self.name)
|
95
|
+
self.replication_schema ||= Webhookdb::Organization::DbBuilder.new(self).default_replication_schema
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.create_if_unique(params)
|
100
|
+
self.db.transaction(savepoint: true) do
|
101
|
+
return Webhookdb::Organization.create(name: params[:name])
|
102
|
+
end
|
103
|
+
rescue Sequel::UniqueConstraintViolation
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def admin_customers
|
108
|
+
return self.verified_memberships.filter(&:admin?).map(&:customer)
|
109
|
+
end
|
110
|
+
|
111
|
+
def alerting
|
112
|
+
return @alerting ||= Alerting.new(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
def cli_editable_fields
|
116
|
+
return ["name", "billing_email"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def readonly_connection(**kw, &)
|
120
|
+
return Webhookdb::ConnectionCache.borrow(self.readonly_connection_url_raw, **kw, &)
|
121
|
+
end
|
122
|
+
|
123
|
+
def admin_connection(**kw, &)
|
124
|
+
return Webhookdb::ConnectionCache.borrow(self.admin_connection_url_raw, **kw, &)
|
125
|
+
end
|
126
|
+
|
127
|
+
def execute_readonly_query(sql)
|
128
|
+
max_rows = self.max_query_rows || self.class.max_query_rows
|
129
|
+
return self.readonly_connection do |conn|
|
130
|
+
ds = conn.fetch(sql)
|
131
|
+
r = QueryResult.new
|
132
|
+
r.max_rows_reached = false
|
133
|
+
r.columns = ds.columns
|
134
|
+
r.rows = []
|
135
|
+
# Stream to avoid pulling in all rows of unlimited queries
|
136
|
+
ds.stream.each do |row|
|
137
|
+
if r.rows.length >= max_rows
|
138
|
+
r.max_rows_reached = true
|
139
|
+
break
|
140
|
+
end
|
141
|
+
r.rows << row.values
|
142
|
+
end
|
143
|
+
return r
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class QueryResult
|
148
|
+
attr_accessor :rows, :columns, :max_rows_reached
|
149
|
+
end
|
150
|
+
|
151
|
+
# Return the readonly connection url, with the host set to public_host if set.
|
152
|
+
def readonly_connection_url
|
153
|
+
return self._public_host_connection_url(self.readonly_connection_url_raw)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Return the admin connection url, with the host set to public_host if set.
|
157
|
+
def admin_connection_url
|
158
|
+
return self._public_host_connection_url(self.admin_connection_url_raw)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Replace the host of the given URL with public_host if it is set,
|
162
|
+
# or return u if not.
|
163
|
+
#
|
164
|
+
# It's very important we store the 'raw' URL to the actual host,
|
165
|
+
# and the public host separately. This will allow us to, for example,
|
166
|
+
# modify the org to point to a new host,
|
167
|
+
# and then update the CNAME (finding it based on the public_host name)
|
168
|
+
# to point to that host.
|
169
|
+
protected def _public_host_connection_url(u)
|
170
|
+
return u if self.public_host.blank?
|
171
|
+
uri = URI(u)
|
172
|
+
uri.host = self.public_host
|
173
|
+
return uri.to_s
|
174
|
+
end
|
175
|
+
|
176
|
+
def dbname
|
177
|
+
raise Webhookdb::InvalidPrecondition, "no db has been created, call prepare_database_connections first" if
|
178
|
+
self.admin_connection_url.blank?
|
179
|
+
return URI(self.admin_connection_url).path.tr("/", "")
|
180
|
+
end
|
181
|
+
|
182
|
+
def admin_user
|
183
|
+
ur = URI(self.admin_connection_url)
|
184
|
+
return ur.user
|
185
|
+
end
|
186
|
+
|
187
|
+
def readonly_user
|
188
|
+
ur = URI(self.readonly_connection_url)
|
189
|
+
return ur.user
|
190
|
+
end
|
191
|
+
|
192
|
+
# In cases where the readonly and admin user are the same, we sometimes adapt queries
|
193
|
+
# to prevent revoking admin db priviliges.
|
194
|
+
def single_db_user?
|
195
|
+
return self.admin_user == self.readonly_user
|
196
|
+
end
|
197
|
+
|
198
|
+
def display_string
|
199
|
+
return "#{self.name} (#{self.key})"
|
200
|
+
end
|
201
|
+
|
202
|
+
def prepare_database_connections?
|
203
|
+
return self.prepare_database_connections(safe: true)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Build the org-specific users, database, and set our connection URLs to it.
|
207
|
+
def prepare_database_connections(safe: false)
|
208
|
+
self.db.transaction do
|
209
|
+
self.lock!
|
210
|
+
if self.admin_connection_url.present?
|
211
|
+
return if safe
|
212
|
+
raise Webhookdb::InvalidPrecondition, "connections already set"
|
213
|
+
end
|
214
|
+
builder = Webhookdb::Organization::DbBuilder.new(self)
|
215
|
+
builder.prepare_database_connections
|
216
|
+
self.admin_connection_url_raw = builder.admin_url
|
217
|
+
self.readonly_connection_url_raw = builder.readonly_url
|
218
|
+
self.save_changes
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Create a CNAME in Cloudflare for the currently configured connection urls.
|
223
|
+
def create_public_host_cname
|
224
|
+
self.db.transaction do
|
225
|
+
self.lock!
|
226
|
+
# We must have a host to create a CNAME to.
|
227
|
+
raise Webhookdb::InvalidPrecondition, "connection urls must be set" if self.readonly_connection_url_raw.blank?
|
228
|
+
# Should only be used once when creating the org DBs.
|
229
|
+
raise Webhookdb::InvalidPrecondition, "public_host must not be set" if self.public_host.present?
|
230
|
+
# Use the raw URL, even though we know at this point
|
231
|
+
# public_host is empty so raw and public host urls are the same.
|
232
|
+
Webhookdb::Organization::DbBuilder.new(self).create_public_host_cname(self.readonly_connection_url_raw)
|
233
|
+
self.save_changes
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Delete the org-specific database and remove the org connection strings.
|
238
|
+
# Use this when an org is to be deleted (either for real, or in test teardown).
|
239
|
+
def remove_related_database
|
240
|
+
self.db.transaction do
|
241
|
+
self.lock!
|
242
|
+
Webhookdb::Organization::DbBuilder.new(self).remove_related_database
|
243
|
+
self.admin_connection_url_raw = ""
|
244
|
+
self.readonly_connection_url_raw = ""
|
245
|
+
self.save_changes
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# As part of the release process, we enqueue a job that will migrate the replication schemas
|
250
|
+
# for all organizations. However this job must use the NEW code being released;
|
251
|
+
# it should not use the CURRENT code the workers may be using when this method is run
|
252
|
+
# during the release process.
|
253
|
+
#
|
254
|
+
# We can get around this by enqueing the jobs with the 'target' release creation date.
|
255
|
+
# Only jobs that execute with this release creation date will perform the migration;
|
256
|
+
# if the job is running using an older release creation date (ie still running old code),
|
257
|
+
# it will re-enqueue the migration to run in the future, using a worker that will eventually
|
258
|
+
# be using newer code.
|
259
|
+
#
|
260
|
+
# For example:
|
261
|
+
#
|
262
|
+
# - We have Release A, created at 0, currently running.
|
263
|
+
# - Release B, created at 1, runs this method.
|
264
|
+
# - The workers, using Release A code (with a release_created_at of 0),
|
265
|
+
# run the ReplicationMigration job.
|
266
|
+
# They see the target release_created_at of 1 is greater than/after the current release_created_at of 0,
|
267
|
+
# so re-enqueue the job.
|
268
|
+
# - Eventually the workers are using Release B code, which has a release_created_at of 1.
|
269
|
+
# This matches the target, so the job is run.
|
270
|
+
#
|
271
|
+
# For a more complex example, which involves releases created in quick succession
|
272
|
+
# (we need to be careful to avoid jobs that never run):
|
273
|
+
#
|
274
|
+
# - We have Release A, created at 0, currently running.
|
275
|
+
# - Release B, created at 1, runs this method.
|
276
|
+
# - Release C, created at 2, runs this method.
|
277
|
+
# - Workers are backed up, so nothing is processed until all workers are using Release C.
|
278
|
+
# - Workers using Release C code process two sets of jobs:
|
279
|
+
# - Jobs with a target release_created_at of 1
|
280
|
+
# - Jobs with a target release_created_at of 2
|
281
|
+
# - Jobs with a target of 2 run the actual migration, because the times match.
|
282
|
+
# - Jobs with a target of 1, see that the target is less than/before current release_created_at of 2.
|
283
|
+
# This indicates the migration is stale, and the job is discarded.
|
284
|
+
#
|
285
|
+
# NOTE: There will always be a race condition where we may process webhooks using the new code,
|
286
|
+
# before we've migrated the replication schemas into the new code. This will error during the upsert
|
287
|
+
# because the column doesn't yet exist. However these will be retried automatically,
|
288
|
+
# and quickly, so we don't worry about them yet.
|
289
|
+
def self.enqueue_migrate_all_replication_tables
|
290
|
+
Webhookdb::Organization.each do |org|
|
291
|
+
Webhookdb::Jobs::ReplicationMigration.perform_in(2, org.id, Webhookdb::RELEASE_CREATED_AT)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Get all the table names and column names for all integrations in the org
|
296
|
+
# Find any of those table/column pairs that are not present in information_schema.columns
|
297
|
+
# Ensure all columns for those integrations/tables.
|
298
|
+
def migrate_replication_tables
|
299
|
+
tables = self.service_integrations.map(&:table_name)
|
300
|
+
sequences_in_app_db = self.db[Sequel[:information_schema][:sequences]].
|
301
|
+
grep(:sequence_name, "replicator_seq_org_#{self.id}_%").
|
302
|
+
select_map(:sequence_name).
|
303
|
+
to_set
|
304
|
+
cols_in_org_db = {}
|
305
|
+
indices_in_org_db = Set.new
|
306
|
+
self.admin_connection do |db|
|
307
|
+
cols_in_org_db = db[Sequel[:information_schema][:columns]].
|
308
|
+
where(table_schema: self.replication_schema, table_name: tables).
|
309
|
+
select(
|
310
|
+
:table_name,
|
311
|
+
Sequel.function(:array_agg, :column_name).cast("text[]").as(:columns),
|
312
|
+
).
|
313
|
+
group_by(:table_name).
|
314
|
+
all.
|
315
|
+
to_h { |c| [c[:table_name], c[:columns]] }
|
316
|
+
indices_in_org_db = db[Sequel[:pg_indexes]].
|
317
|
+
where(schemaname: self.replication_schema, tablename: tables).
|
318
|
+
select_map(:indexname).
|
319
|
+
to_set
|
320
|
+
end
|
321
|
+
|
322
|
+
self.service_integrations.each do |sint|
|
323
|
+
svc = sint.replicator
|
324
|
+
existing_columns = cols_in_org_db.fetch(sint.table_name) { [] }
|
325
|
+
cols_for_sint = svc.storable_columns.map { |c| c.name.to_s }
|
326
|
+
all_sint_cols_exist = (cols_for_sint - existing_columns).empty?
|
327
|
+
|
328
|
+
all_indices_exist = svc.indices(svc.dbadapter_table).all? do |ind|
|
329
|
+
indices_in_org_db.include?(ind.name.to_s)
|
330
|
+
end
|
331
|
+
|
332
|
+
svc.ensure_all_columns unless all_sint_cols_exist && all_indices_exist
|
333
|
+
if svc.requires_sequence? && !sequences_in_app_db.include?(sint.sequence_name)
|
334
|
+
sint.ensure_sequence(skip_check: true)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Modify the admin and readonly users to have new usernames and passwords.
|
340
|
+
def roll_database_credentials
|
341
|
+
self.db.transaction do
|
342
|
+
self.lock!
|
343
|
+
builder = Webhookdb::Organization::DbBuilder.new(self)
|
344
|
+
builder.roll_connection_credentials
|
345
|
+
self.admin_connection_url_raw = builder.admin_url
|
346
|
+
self.readonly_connection_url_raw = builder.readonly_url
|
347
|
+
self.save_changes
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def migrate_replication_schema(schema)
|
352
|
+
unless Webhookdb::DBAdapter::VALID_IDENTIFIER.match?(schema)
|
353
|
+
msg = "Sorry, this is not a valid schema name. " + Webhookdb::DBAdapter::INVALID_IDENTIFIER_MESSAGE
|
354
|
+
raise SchemaMigrationError, msg
|
355
|
+
end
|
356
|
+
Webhookdb::Organization::DatabaseMigration.guard_ongoing!(self)
|
357
|
+
raise SchemaMigrationError, "destination and target schema are the same" if schema == self.replication_schema
|
358
|
+
builder = Webhookdb::Organization::DbBuilder.new(self)
|
359
|
+
sql = builder.migration_replication_schema_sql(self.replication_schema, schema)
|
360
|
+
self.admin_connection(transaction: true) do |db|
|
361
|
+
db << sql
|
362
|
+
end
|
363
|
+
self.update(replication_schema: schema)
|
364
|
+
end
|
365
|
+
|
366
|
+
def register_in_stripe
|
367
|
+
raise Webhookdb::InvalidPrecondition, "org already in Stripe" if self.stripe_customer_id.present?
|
368
|
+
stripe_customer = Stripe::Customer.create(
|
369
|
+
{
|
370
|
+
name: self.name,
|
371
|
+
email: self.billing_email,
|
372
|
+
metadata: {
|
373
|
+
org_id: self.id,
|
374
|
+
},
|
375
|
+
},
|
376
|
+
)
|
377
|
+
self.stripe_customer_id = stripe_customer.id
|
378
|
+
self.save_changes
|
379
|
+
return stripe_customer
|
380
|
+
end
|
381
|
+
|
382
|
+
def get_stripe_billing_portal_url
|
383
|
+
raise Webhookdb::InvalidPrecondition, "organization must be registered in Stripe" if self.stripe_customer_id.blank?
|
384
|
+
session = Stripe::BillingPortal::Session.create(
|
385
|
+
{
|
386
|
+
customer: self.stripe_customer_id,
|
387
|
+
return_url: Webhookdb.app_url + "/jump/portal-return",
|
388
|
+
},
|
389
|
+
)
|
390
|
+
|
391
|
+
return session.url
|
392
|
+
end
|
393
|
+
|
394
|
+
def get_stripe_checkout_url(price_id)
|
395
|
+
raise Webhookdb::InvalidPrecondition, "organization must be registered in Stripe" if self.stripe_customer_id.blank?
|
396
|
+
session = Stripe::Checkout::Session.create(
|
397
|
+
{
|
398
|
+
customer: self.stripe_customer_id,
|
399
|
+
cancel_url: Webhookdb.app_url + "/jump/checkout-cancel",
|
400
|
+
line_items: [{
|
401
|
+
price: price_id, quantity: 1,
|
402
|
+
}],
|
403
|
+
mode: "subscription",
|
404
|
+
payment_method_types: ["card"],
|
405
|
+
allow_promotion_codes: true,
|
406
|
+
success_url: Webhookdb.app_url + "/jump/checkout-success",
|
407
|
+
},
|
408
|
+
)
|
409
|
+
|
410
|
+
return session.url
|
411
|
+
end
|
412
|
+
|
413
|
+
#
|
414
|
+
# :section: Memberships
|
415
|
+
#
|
416
|
+
|
417
|
+
def add_membership(opts={})
|
418
|
+
if !opts.is_a?(Webhookdb::OrganizationMembership) && !opts.key?(:verified)
|
419
|
+
raise ArgumentError, "must pass :verified or a model into add_membership, it is ambiguous otherwise"
|
420
|
+
end
|
421
|
+
self.associations.delete(opts[:verified] ? :verified_memberships : :invited_memberships)
|
422
|
+
return self.add_all_membership(opts)
|
423
|
+
end
|
424
|
+
|
425
|
+
# SUBSCRIPTION PERMISSIONS
|
426
|
+
|
427
|
+
def active_subscription?
|
428
|
+
subscription = Webhookdb::Subscription[stripe_customer_id: self.stripe_customer_id]
|
429
|
+
# return false if no subscription
|
430
|
+
return false if subscription.nil?
|
431
|
+
# otherwise check stripe subscription string
|
432
|
+
return ["trialing", "active", "past due"].include? subscription.status
|
433
|
+
end
|
434
|
+
|
435
|
+
def can_add_new_integration?
|
436
|
+
# if the sint's organization has an active subscription, return true
|
437
|
+
return true if self.active_subscription?
|
438
|
+
# if there is no active subscription, check number of integrations against free tier max
|
439
|
+
limit = Webhookdb::Subscription.max_free_integrations
|
440
|
+
return Webhookdb::ServiceIntegration.where(organization: self).count < limit
|
441
|
+
end
|
442
|
+
|
443
|
+
def available_replicator_names
|
444
|
+
available = Webhookdb::Replicator.registry.values.filter do |desc|
|
445
|
+
# The org must have any of the flags required for the service. In other words,
|
446
|
+
# the intersection of desc[:feature_roles] & org.feature_roles must
|
447
|
+
# not be empty
|
448
|
+
no_restrictions = desc.feature_roles.empty?
|
449
|
+
next true if no_restrictions
|
450
|
+
org_has_access = (self.feature_roles.map(&:name) & desc.feature_roles).present?
|
451
|
+
org_has_access
|
452
|
+
end
|
453
|
+
return available.map(&:name)
|
454
|
+
end
|
455
|
+
|
456
|
+
#
|
457
|
+
# :section: Validations
|
458
|
+
#
|
459
|
+
|
460
|
+
def validate
|
461
|
+
super
|
462
|
+
validates_all_or_none(:admin_connection_url_raw, :readonly_connection_url_raw, predicate: :present?)
|
463
|
+
validates_format(/^\D/, :name, message: "can't begin with a digit")
|
464
|
+
validates_format(/^[a-z][a-z0-9_]*$/, :key, message: "is not valid as a CNAME")
|
465
|
+
validates_max_length 63, :key, message: "is not valid as a CNAME"
|
466
|
+
end
|
467
|
+
|
468
|
+
# @!attribute service_integrations
|
469
|
+
# @return [Array<Webhookdb::ServiceIntegration>]
|
470
|
+
end
|
471
|
+
|
472
|
+
require "webhookdb/organization/alerting"
|
473
|
+
require "webhookdb/organization/db_builder"
|
474
|
+
|
475
|
+
# Table: organizations
|
476
|
+
# ------------------------------------------------------------------------------------------------------------------------------------------------------------
|
477
|
+
# Columns:
|
478
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
479
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
480
|
+
# updated_at | timestamp with time zone |
|
481
|
+
# soft_deleted_at | timestamp with time zone |
|
482
|
+
# name | text | NOT NULL
|
483
|
+
# key | text |
|
484
|
+
# billing_email | text | NOT NULL DEFAULT ''::text
|
485
|
+
# stripe_customer_id | text | NOT NULL DEFAULT ''::text
|
486
|
+
# readonly_connection_url_raw | text |
|
487
|
+
# admin_connection_url_raw | text |
|
488
|
+
# public_host | text | NOT NULL DEFAULT ''::text
|
489
|
+
# cloudflare_dns_record_json | jsonb | NOT NULL DEFAULT '{}'::jsonb
|
490
|
+
# replication_schema | text | NOT NULL
|
491
|
+
# job_semaphore_size | integer | NOT NULL DEFAULT 10
|
492
|
+
# minimum_sync_seconds | integer | NOT NULL
|
493
|
+
# sync_target_timeout | integer | NOT NULL DEFAULT 30
|
494
|
+
# max_query_rows | integer |
|
495
|
+
# Indexes:
|
496
|
+
# organizations_pkey | PRIMARY KEY btree (id)
|
497
|
+
# organizations_key_key | UNIQUE btree (key)
|
498
|
+
# organizations_name_key | UNIQUE btree (name)
|
499
|
+
# Referenced By:
|
500
|
+
# feature_roles_organizations | feature_roles_organizations_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
501
|
+
# logged_webhooks | logged_webhooks_organization_id_fkey | (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
|
502
|
+
# organization_database_migrations | organization_database_migrations_organization_id_fkey | (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
|
503
|
+
# organization_memberships | organization_memberships_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
504
|
+
# service_integrations | service_integrations_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
505
|
+
# webhook_subscriptions | webhook_subscriptions_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
506
|
+
# ------------------------------------------------------------------------------------------------------------------------------------------------------------
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/postgres/model"
|
4
|
+
|
5
|
+
class Webhookdb::OrganizationMembership < Webhookdb::Postgres::Model(:organization_memberships)
|
6
|
+
VALID_ROLE_NAMES = ["admin", "member"].freeze
|
7
|
+
|
8
|
+
many_to_one :organization, class: "Webhookdb::Organization"
|
9
|
+
many_to_one :customer, class: "Webhookdb::Customer"
|
10
|
+
many_to_one :membership_role, class: "Webhookdb::Role"
|
11
|
+
|
12
|
+
def verified?
|
13
|
+
return self.verified
|
14
|
+
end
|
15
|
+
|
16
|
+
def default?
|
17
|
+
return self.is_default
|
18
|
+
end
|
19
|
+
|
20
|
+
def customer_email
|
21
|
+
return self.customer.email
|
22
|
+
end
|
23
|
+
|
24
|
+
def organization_name
|
25
|
+
return self.organization.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def status
|
29
|
+
return "invited" unless self.verified
|
30
|
+
self.membership_role.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def admin?
|
34
|
+
return self.membership_role.name == "admin"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Table: organization_memberships
|
39
|
+
# ------------------------------------------------------------------------------------------------------------------------------
|
40
|
+
# Columns:
|
41
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
42
|
+
# customer_id | integer | NOT NULL
|
43
|
+
# organization_id | integer | NOT NULL
|
44
|
+
# verified | boolean | NOT NULL
|
45
|
+
# invitation_code | text | NOT NULL DEFAULT ''::text
|
46
|
+
# membership_role_id | integer | NOT NULL
|
47
|
+
# is_default | boolean | NOT NULL DEFAULT false
|
48
|
+
# Indexes:
|
49
|
+
# organization_memberships_pkey | PRIMARY KEY btree (id)
|
50
|
+
# one_default_per_customer | UNIQUE btree (customer_id, organization_id) WHERE is_default IS TRUE
|
51
|
+
# Check constraints:
|
52
|
+
# default_is_verified | (is_default IS TRUE AND verified IS TRUE OR is_default IS FALSE)
|
53
|
+
# invited_has_code | (verified IS TRUE AND length(invitation_code) < 1 OR verified IS FALSE AND length(invitation_code) > 0)
|
54
|
+
# Foreign key constraints:
|
55
|
+
# organization_memberships_customer_id_fkey | (customer_id) REFERENCES customers(id)
|
56
|
+
# organization_memberships_membership_role_id_fkey | (membership_role_id) REFERENCES roles(id)
|
57
|
+
# organization_memberships_organization_id_fkey | (organization_id) REFERENCES organizations(id)
|
58
|
+
# ------------------------------------------------------------------------------------------------------------------------------
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webhookdb::PhoneNumber
|
4
|
+
class US
|
5
|
+
REGEXP = /^1[0-9]{10}$/
|
6
|
+
|
7
|
+
def self.normalize(s)
|
8
|
+
norm = Phony.normalize(s, cc: "1")
|
9
|
+
norm = "1#{norm}" if norm.length == 10 && norm.first == "1"
|
10
|
+
return norm
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid?(s)
|
14
|
+
return false if s.nil?
|
15
|
+
return self.valid_normalized?(self.normalize(s))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.valid_normalized?(s)
|
19
|
+
return REGEXP.match?(s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.format(s)
|
23
|
+
raise ArgumentError, "#{s} must be a normalized to #{REGEXP}" unless self.valid_normalized?(s)
|
24
|
+
return "(#{s[1..3]}) #{s[4..6]}-#{s[7..]}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Given a string representing a phone number, returns that phone number in E.164 format (+1XXX5550100).
|
29
|
+
# Assumes all provided phone numbers are US numbers.
|
30
|
+
# Does not check for invalid area codes.
|
31
|
+
def self.format_e164(phone)
|
32
|
+
return nil if phone.blank?
|
33
|
+
return phone if /^\+1\d{10}$/.match?(phone)
|
34
|
+
phone = phone.gsub(/\D/, "")
|
35
|
+
return "+1" + phone if phone.size == 10
|
36
|
+
return "+" + phone if phone.size == 11
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "appydays/configurable"
|
4
|
+
|
5
|
+
module Webhookdb::Plaid
|
6
|
+
include Appydays::Configurable
|
7
|
+
|
8
|
+
configurable(:plaid) do
|
9
|
+
setting :page_size, 100
|
10
|
+
setting :http_timeout, 30
|
11
|
+
end
|
12
|
+
|
13
|
+
# Manual backfilling is not supported on Plaid integrations.
|
14
|
+
# If a manual backfill is attempted, direct customer to this url.
|
15
|
+
DOCUMENTATION_URL = "https://docs.webhookdb.com/guides/plaid/"
|
16
|
+
|
17
|
+
def self.webhook_response(request, webhook_secret)
|
18
|
+
# Eventually we can figure out how to verify Plaid webhooks,
|
19
|
+
# but it's sort of crazy so ignore it for now.
|
20
|
+
return Webhookdb::WebhookResponse.ok(status: 202) if request.env["HTTP_PLAID_VERIFICATION"]
|
21
|
+
return Webhookdb::WebhookResponse.for_standard_secret(request, webhook_secret, ok_status: 200)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webhookdb::Platform
|
4
|
+
PLATFORM_USER_AGENT_HTTP = "HTTP_WHDB_PLATFORM_USER_AGENT"
|
5
|
+
CLI_USER_AGENT_HTTP = "HTTP_WHDB_USER_AGENT"
|
6
|
+
USER_AGENT_HTTP = "HTTP_USER_AGENT"
|
7
|
+
HEADERS = [
|
8
|
+
PLATFORM_USER_AGENT_HTTP,
|
9
|
+
CLI_USER_AGENT_HTTP,
|
10
|
+
USER_AGENT_HTTP,
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
# Return the value of the platform UA header.
|
14
|
+
# For WASM, this is the browser's user agent.
|
15
|
+
# For a binary, this is empty.
|
16
|
+
def self.platform_user_agent(env)
|
17
|
+
return env[PLATFORM_USER_AGENT_HTTP] || ""
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return the user agent in the env.
|
21
|
+
# This should be the CLI user agent,
|
22
|
+
# though it may also be a browser in WASM depending on the browser.
|
23
|
+
def self.user_agent(env)
|
24
|
+
values = HEADERS.map { |h| env[h] }
|
25
|
+
return values.find(&:present?) || ""
|
26
|
+
end
|
27
|
+
end
|