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,347 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "bcrypt"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "webhookdb/id"
|
|
7
|
+
require "webhookdb/postgres/model"
|
|
8
|
+
require "webhookdb/demo_mode"
|
|
9
|
+
|
|
10
|
+
class Webhookdb::Customer < Webhookdb::Postgres::Model(:customers)
|
|
11
|
+
extend Webhookdb::MethodUtilities
|
|
12
|
+
include Appydays::Configurable
|
|
13
|
+
|
|
14
|
+
class InvalidPassword < StandardError; end
|
|
15
|
+
class SignupDisabled < StandardError; end
|
|
16
|
+
|
|
17
|
+
configurable(:customer) do
|
|
18
|
+
setting :signup_email_allowlist, ["*"], convert: ->(s) { s.split }
|
|
19
|
+
setting :skip_authentication, false
|
|
20
|
+
setting :skip_authentication_allowlist, [], convert: ->(s) { s.split }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The bcrypt hash cost. Changing this would invalidate all passwords!
|
|
24
|
+
# It's only here so we can change it for testing.
|
|
25
|
+
singleton_attr_accessor :password_hash_cost
|
|
26
|
+
@password_hash_cost = 10
|
|
27
|
+
|
|
28
|
+
MIN_PASSWORD_LENGTH = 8
|
|
29
|
+
|
|
30
|
+
# A bcrypt digest that's valid, but not a real digest. Used as a placeholder for
|
|
31
|
+
# accounts with no passwords, which makes them impossible to authenticate. Or at
|
|
32
|
+
# least much less likely than with a random string.
|
|
33
|
+
PLACEHOLDER_PASSWORD_DIGEST = "$2a$11$....................................................."
|
|
34
|
+
|
|
35
|
+
# Regex that matches the prefix of a deleted user's email
|
|
36
|
+
DELETED_EMAIL_PATTERN = /^(?<prefix>\d+(?:\.\d+)?)\+(?<rest>.*)$/
|
|
37
|
+
|
|
38
|
+
plugin :timestamps
|
|
39
|
+
plugin :soft_deletes
|
|
40
|
+
|
|
41
|
+
one_to_many :all_memberships, class: "Webhookdb::OrganizationMembership"
|
|
42
|
+
one_to_many :invited_memberships,
|
|
43
|
+
class: "Webhookdb::OrganizationMembership",
|
|
44
|
+
conditions: {verified: false},
|
|
45
|
+
adder: ->(om) { om.update(customer_id: id, verified: false) }
|
|
46
|
+
one_to_many :verified_memberships,
|
|
47
|
+
class: "Webhookdb::OrganizationMembership",
|
|
48
|
+
conditions: {verified: true},
|
|
49
|
+
adder: ->(om) { om.update(customer_id: id, verified: true) }
|
|
50
|
+
one_to_one :default_membership, class: "Webhookdb::OrganizationMembership", conditions: {is_default: true}
|
|
51
|
+
one_to_many :message_deliveries, key: :recipient_id, class: "Webhookdb::Message::Delivery"
|
|
52
|
+
one_to_many :reset_codes, class: "Webhookdb::Customer::ResetCode", order: Sequel.desc([:created_at])
|
|
53
|
+
many_to_many :roles, class: "Webhookdb::Role", join_table: :roles_customers
|
|
54
|
+
|
|
55
|
+
dataset_module do
|
|
56
|
+
def with_email(*emails)
|
|
57
|
+
emails = emails.map { |e| e.downcase.strip }
|
|
58
|
+
return self.where(email: emails)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.with_email(e)
|
|
63
|
+
return self.dataset.with_email(e).first
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.find_or_create_for_email(email)
|
|
67
|
+
email = email.strip.downcase
|
|
68
|
+
# If there is no Customer object associated with the email, create one
|
|
69
|
+
me = Webhookdb::Customer[email:]
|
|
70
|
+
return [false, me] if me
|
|
71
|
+
signup_allowed = self.signup_email_allowlist.any? { |pattern| File.fnmatch(pattern, email) }
|
|
72
|
+
raise SignupDisabled unless signup_allowed
|
|
73
|
+
return [true, Webhookdb::Customer.create(email:, password: SecureRandom.hex(32))]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Make sure the customer has a default organization.
|
|
77
|
+
# New registrants, or users who have been invited (so have an existing customer and invited org)
|
|
78
|
+
# get an org created. Default orgs must already be verified as per a DB constraint.
|
|
79
|
+
# @return [Array<TrueClass,FalseClass,Webhookdb::OrganizationMembership>] Tuple of [created, membership]
|
|
80
|
+
def self.find_or_create_default_organization(customer)
|
|
81
|
+
mem = customer.default_membership
|
|
82
|
+
return [false, mem] if mem
|
|
83
|
+
email = customer.email
|
|
84
|
+
# We could have no default, but already be in an organization, like if the default was deleted.
|
|
85
|
+
mem = customer.verified_memberships.first
|
|
86
|
+
return [false, mem] if mem
|
|
87
|
+
# We have no verified orgs, so create one.
|
|
88
|
+
# TODO: this will fail if not unique. We need to make sure we pick a unique name/key.
|
|
89
|
+
self_org = Webhookdb::Organization.create(name: "#{email} Org", billing_email: email.to_s)
|
|
90
|
+
mem = customer.add_membership(
|
|
91
|
+
organization: self_org, membership_role: Webhookdb::Role.admin_role, verified: true, is_default: true,
|
|
92
|
+
)
|
|
93
|
+
self_org.publish_deferred("syncdemodata", self_org.id) if Webhookdb::DemoMode.example_datasets_enabled
|
|
94
|
+
return [true, mem]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return Tuple of <Step, Customer>.
|
|
98
|
+
def self.register_or_login(email:)
|
|
99
|
+
self.db.transaction do
|
|
100
|
+
customer_created, me = self.find_or_create_for_email(email)
|
|
101
|
+
org_created, _membership = self.find_or_create_default_organization(me)
|
|
102
|
+
me.reset_codes_dataset.usable.each(&:expire!)
|
|
103
|
+
me.add_reset_code(transport: "email")
|
|
104
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
105
|
+
step.output = if customer_created || org_created
|
|
106
|
+
%(To finish registering, please look for an email we just sent to #{email}.
|
|
107
|
+
It contains a One Time Password code to validate your email.
|
|
108
|
+
)
|
|
109
|
+
else
|
|
110
|
+
%(Hello again!
|
|
111
|
+
|
|
112
|
+
To finish logging in, please look for an email we just sent to #{email}.
|
|
113
|
+
It contains a One Time Password used to log in.
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
step.output += %(You can enter it here, or if you want to finish up from a new prompt, use:
|
|
117
|
+
|
|
118
|
+
webhookdb auth login --username=#{email} --token=<#{Webhookdb::Customer::ResetCode::TOKEN_LENGTH} digit token>
|
|
119
|
+
)
|
|
120
|
+
step.prompt = "Enter the token from your email:"
|
|
121
|
+
step.prompt_is_secret = true
|
|
122
|
+
step.needs_input = true
|
|
123
|
+
step.post_to_url = "/v1/auth"
|
|
124
|
+
step.post_params = {email:}
|
|
125
|
+
step.post_params_value_key = "token"
|
|
126
|
+
return [step, me]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @return Tuple of <Step, Customer>. Customer is nil if token was invalid.
|
|
131
|
+
def self.finish_otp(me, token:)
|
|
132
|
+
if me.nil?
|
|
133
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
134
|
+
step.output = %(Sorry, no one with that email exists. Try running:
|
|
135
|
+
|
|
136
|
+
webhookdb auth login [email]
|
|
137
|
+
)
|
|
138
|
+
step.needs_input = false
|
|
139
|
+
step.complete = true
|
|
140
|
+
step.error_code = "email_not_exist"
|
|
141
|
+
return [step, nil]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
unless me.should_skip_authentication?
|
|
145
|
+
begin
|
|
146
|
+
Webhookdb::Customer::ResetCode.use_code_with_token(token) do |code|
|
|
147
|
+
raise Webhookdb::Customer::ResetCode::Unusable unless code.customer === me
|
|
148
|
+
code.customer.save_changes
|
|
149
|
+
me.refresh
|
|
150
|
+
end
|
|
151
|
+
rescue Webhookdb::Customer::ResetCode::Unusable
|
|
152
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
153
|
+
step.output = %(Sorry, that token is invalid. Please try again.
|
|
154
|
+
If you have not gotten a code, use Ctrl+C to close this prompt and request a new code:
|
|
155
|
+
|
|
156
|
+
webhookdb auth login #{me.email}
|
|
157
|
+
)
|
|
158
|
+
step.error_code = "invalid_otp"
|
|
159
|
+
step.prompt_is_secret = true
|
|
160
|
+
step.prompt = "Enter the token from your email:"
|
|
161
|
+
step.needs_input = true
|
|
162
|
+
step.post_to_url = "/v1/auth"
|
|
163
|
+
step.post_params = {email: me.email}
|
|
164
|
+
step.post_params_value_key = "token"
|
|
165
|
+
return [step, nil]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
welcome_tutorial = "Quick tip: Use `webhookdb services list` to see what services are available."
|
|
170
|
+
if me.invited_memberships.present?
|
|
171
|
+
welcome_tutorial = "You have the following pending invites:\n\n" +
|
|
172
|
+
me.invited_memberships.map { |om| " #{om.organization.display_string}: #{om.invitation_code}" }.join("\n") +
|
|
173
|
+
"\n\nUse `webhookdb org join [code]` to accept an invitation."
|
|
174
|
+
end
|
|
175
|
+
step = Webhookdb::Replicator::StateMachineStep.new
|
|
176
|
+
step.output = %(Welcome! For help getting started, please check out
|
|
177
|
+
our docs at https://docs.webhookdb.com.
|
|
178
|
+
|
|
179
|
+
#{welcome_tutorial})
|
|
180
|
+
step.needs_input = false
|
|
181
|
+
step.complete = true
|
|
182
|
+
return [step, me]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# If the SKIP_PHONE|EMAIL_VERIFICATION are set, verify the phone/email.
|
|
186
|
+
# Also verify phone and email if the customer email matches the allowlist.
|
|
187
|
+
def should_skip_authentication?
|
|
188
|
+
return true if self.class.skip_authentication
|
|
189
|
+
return true if self.class.skip_authentication_allowlist.any? { |pattern| File.fnmatch(pattern, self.email) }
|
|
190
|
+
return false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def ensure_role(role_or_name)
|
|
194
|
+
role = role_or_name.is_a?(Webhookdb::Role) ? role_or_name : Webhookdb::Role[name: role_or_name]
|
|
195
|
+
raise "No role for #{role_or_name}" unless role.present?
|
|
196
|
+
self.add_role(role) unless self.roles_dataset[role.id]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def admin?
|
|
200
|
+
return self.roles.include?(Webhookdb::Role.admin_role)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def greeting
|
|
204
|
+
return self.name.present? ? self.name : "there"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
#
|
|
208
|
+
# :section: Memberships
|
|
209
|
+
#
|
|
210
|
+
|
|
211
|
+
def add_membership(opts={})
|
|
212
|
+
if !opts.is_a?(Webhookdb::OrganizationMembership) && !opts.key?(:verified)
|
|
213
|
+
raise ArgumentError, "must pass :verified or a model into add_membership, it is ambiguous otherwise"
|
|
214
|
+
end
|
|
215
|
+
self.associations.delete(opts[:verified] ? :verified_memberships : :invited_memberships)
|
|
216
|
+
return self.add_all_membership(opts)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def verified_member_of?(org)
|
|
220
|
+
return !org.verified_memberships_dataset.where(customer_id: self.id).empty?
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def default_organization
|
|
224
|
+
return self.default_membership&.organization
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def replace_default_membership(new_mem)
|
|
228
|
+
self.verified_memberships_dataset.update(is_default: false)
|
|
229
|
+
self.associations.delete(:verified_memberships)
|
|
230
|
+
new_mem.update(is_default: true)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
#
|
|
234
|
+
# :section: Password
|
|
235
|
+
#
|
|
236
|
+
|
|
237
|
+
### Fetch the user's password as an BCrypt::Password object.
|
|
238
|
+
def encrypted_password
|
|
239
|
+
digest = self.password_digest or return nil
|
|
240
|
+
return BCrypt::Password.new(digest)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
### Set the password to the given +unencrypted+ String.
|
|
244
|
+
def password=(unencrypted)
|
|
245
|
+
if unencrypted
|
|
246
|
+
self.check_password_complexity(unencrypted)
|
|
247
|
+
self.password_digest = BCrypt::Password.create(unencrypted, cost: self.class.password_hash_cost)
|
|
248
|
+
else
|
|
249
|
+
self.password_digest = BCrypt::Password.new(PLACEHOLDER_PASSWORD_DIGEST)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
### Attempt to authenticate the user with the specified +unencrypted+ password. Returns
|
|
254
|
+
### +true+ if the password matched.
|
|
255
|
+
def authenticate(unencrypted)
|
|
256
|
+
return false unless unencrypted
|
|
257
|
+
return false if self.soft_deleted?
|
|
258
|
+
return self.encrypted_password == unencrypted
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
protected def new_password_matches?(unencrypted)
|
|
262
|
+
existing_pw = BCrypt::Password.new(self.password_digest)
|
|
263
|
+
new_pw = self.digest_password(unencrypted)
|
|
264
|
+
return existing_pw == new_pw
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
### Raise if +unencrypted+ password does not meet complexity requirements.
|
|
268
|
+
protected def check_password_complexity(unencrypted)
|
|
269
|
+
raise Webhookdb::Customer::InvalidPassword, "password must be at least %d characters." % [MIN_PASSWORD_LENGTH] if
|
|
270
|
+
unencrypted.length < MIN_PASSWORD_LENGTH
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
#
|
|
274
|
+
# :section: Phone
|
|
275
|
+
#
|
|
276
|
+
|
|
277
|
+
def us_phone
|
|
278
|
+
return Phony.format(self.phone, format: :national)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def us_phone=(s)
|
|
282
|
+
self.phone = Webhookdb::PhoneNumber::US.normalize(s)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def unverified?
|
|
286
|
+
return !self.email_verified? && !self.phone_verified?
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
#
|
|
290
|
+
# :section: Sequel Hooks
|
|
291
|
+
#
|
|
292
|
+
|
|
293
|
+
def before_create
|
|
294
|
+
self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("cus")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
### Soft-delete hook -- prep the user for deletion.
|
|
298
|
+
def before_soft_delete
|
|
299
|
+
self.email = "#{Time.now.to_f}+#{self[:email]}"
|
|
300
|
+
self.password = "aA1!#{SecureRandom.hex(8)}"
|
|
301
|
+
super
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
### Soft-delete hook -- expire unused, unexpired reset codes and
|
|
305
|
+
### trigger an event on removal.
|
|
306
|
+
|
|
307
|
+
#
|
|
308
|
+
# :section: Sequel Validation
|
|
309
|
+
#
|
|
310
|
+
|
|
311
|
+
def validate
|
|
312
|
+
super
|
|
313
|
+
self.validates_presence(:email)
|
|
314
|
+
self.validates_format(/[[:graph:]]+@[[:graph:]]+\.[a-zA-Z]{2,}/, :email)
|
|
315
|
+
self.validates_unique(:email)
|
|
316
|
+
self.validates_operator(:==, self.email&.downcase&.strip, :email)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Table: customers
|
|
321
|
+
# -----------------------------------------------------------------------------------------------------------------------------------------------------
|
|
322
|
+
# Columns:
|
|
323
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
324
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
325
|
+
# updated_at | timestamp with time zone |
|
|
326
|
+
# soft_deleted_at | timestamp with time zone |
|
|
327
|
+
# password_digest | text | NOT NULL
|
|
328
|
+
# email | citext | NOT NULL
|
|
329
|
+
# name | text | NOT NULL DEFAULT ''::text
|
|
330
|
+
# note | text | NOT NULL DEFAULT ''::text
|
|
331
|
+
# opaque_id | text | NOT NULL
|
|
332
|
+
# Indexes:
|
|
333
|
+
# customers_pkey | PRIMARY KEY btree (id)
|
|
334
|
+
# customers_email_key | UNIQUE btree (email)
|
|
335
|
+
# customers_opaque_id_key | UNIQUE btree (opaque_id)
|
|
336
|
+
# Check constraints:
|
|
337
|
+
# lowercase_nospace_email | (email::text = btrim(lower(email::text)))
|
|
338
|
+
# Referenced By:
|
|
339
|
+
# backfill_jobs | backfill_jobs_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
340
|
+
# customer_reset_codes | customer_reset_codes_customer_id_fkey | (customer_id) REFERENCES customers(id) ON DELETE CASCADE
|
|
341
|
+
# message_deliveries | message_deliveries_recipient_id_fkey | (recipient_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
342
|
+
# organization_database_migrations | organization_database_migrations_started_by_id_fkey | (started_by_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
343
|
+
# organization_memberships | organization_memberships_customer_id_fkey | (customer_id) REFERENCES customers(id)
|
|
344
|
+
# roles_customers | roles_customers_customer_id_fkey | (customer_id) REFERENCES customers(id)
|
|
345
|
+
# sync_targets | sync_targets_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
346
|
+
# webhook_subscriptions | webhook_subscriptions_created_by_id_fkey | (created_by_id) REFERENCES customers(id)
|
|
347
|
+
# -----------------------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
|
|
5
|
+
# Simple model for jamming stuff into the database.
|
|
6
|
+
# Since WebhookDB isn't a resource-heavy application,
|
|
7
|
+
# and it's meant to be self-hosted,
|
|
8
|
+
# we may as well do this over pulling in an S3 or GCS dependeency.
|
|
9
|
+
class Webhookdb::DatabaseDocument < Webhookdb::Postgres::Model(:database_documents)
|
|
10
|
+
include Appydays::Configurable
|
|
11
|
+
configurable(:database_document) do
|
|
12
|
+
setting :skip_authentication, false
|
|
13
|
+
setting :skip_authentication_allowlist, [], convert: ->(s) { s.split }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
plugin :column_encryption do |enc|
|
|
17
|
+
enc.column :encryption_secret
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(*)
|
|
21
|
+
super
|
|
22
|
+
self.encryption_secret ||= SecureRandom.hex(32)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def sign_url(path, expire_at:, params: {})
|
|
26
|
+
uri = URI(path)
|
|
27
|
+
q = params.merge(expire_at: expire_at.to_i)
|
|
28
|
+
uri.query = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER.call(q)
|
|
29
|
+
url = uri.to_s
|
|
30
|
+
sig = self.digest_url(url)
|
|
31
|
+
return url + "&sig=#{sig}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def check_url(url, now: Time.now)
|
|
35
|
+
sig_idx = url.rindex("&sig=")
|
|
36
|
+
return false if sig_idx.nil?
|
|
37
|
+
without_sig = url[...sig_idx]
|
|
38
|
+
got_sig = url[(sig_idx + 5)..]
|
|
39
|
+
real_sig = self.digest_url(without_sig)
|
|
40
|
+
return false unless ActiveSupport::SecurityUtils.secure_compare(got_sig, real_sig)
|
|
41
|
+
expires = CGI.parse(URI(url).query || "?")["expire_at"]
|
|
42
|
+
return false unless expires
|
|
43
|
+
t = Time.at(expires.first.to_i)
|
|
44
|
+
return false if t <= now
|
|
45
|
+
return true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected def digest_url(url)
|
|
49
|
+
hmac = OpenSSL::HMAC.digest("sha256", self.encryption_secret, url)
|
|
50
|
+
b = Base64.urlsafe_encode64(hmac, padding: false)
|
|
51
|
+
return b
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def presigned_view_url(expire_at:, **kw)
|
|
55
|
+
url = "#{Webhookdb.api_url}/admin/v1/database_documents/#{self.id}/view"
|
|
56
|
+
return self.sign_url(url, expire_at:, **kw)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Table: database_documents
|
|
61
|
+
# --------------------------------------------------------------------------------------------
|
|
62
|
+
# Columns:
|
|
63
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
64
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
65
|
+
# key | text | NOT NULL
|
|
66
|
+
# content | bytea | NOT NULL
|
|
67
|
+
# content_type | text | NOT NULL
|
|
68
|
+
# encryption_secret | text | NOT NULL
|
|
69
|
+
# Indexes:
|
|
70
|
+
# database_documents_pkey | PRIMARY KEY btree (id)
|
|
71
|
+
# database_documents_key_key | UNIQUE btree (key)
|
|
72
|
+
# --------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::DBAdapter::ColumnTypes
|
|
4
|
+
BIGINT = :bigint
|
|
5
|
+
BIGINT_ARRAY = :bigintarray
|
|
6
|
+
BOOLEAN = :bool
|
|
7
|
+
DATE = :date
|
|
8
|
+
DECIMAL = :decimal
|
|
9
|
+
DOUBLE = :double
|
|
10
|
+
FLOAT = :float
|
|
11
|
+
INTEGER = :int
|
|
12
|
+
INTEGER_ARRAY = :intarray
|
|
13
|
+
TEXT_ARRAY = :textarray
|
|
14
|
+
OBJECT = :object
|
|
15
|
+
TEXT = :text
|
|
16
|
+
TIMESTAMP = :timestamp
|
|
17
|
+
UUID = :uuid
|
|
18
|
+
|
|
19
|
+
COLUMN_TYPES = Set.new(
|
|
20
|
+
[
|
|
21
|
+
BIGINT,
|
|
22
|
+
BIGINT_ARRAY,
|
|
23
|
+
BOOLEAN,
|
|
24
|
+
DATE,
|
|
25
|
+
DECIMAL,
|
|
26
|
+
DOUBLE,
|
|
27
|
+
FLOAT,
|
|
28
|
+
INTEGER,
|
|
29
|
+
INTEGER_ARRAY,
|
|
30
|
+
OBJECT,
|
|
31
|
+
TEXT,
|
|
32
|
+
TEXT_ARRAY,
|
|
33
|
+
TIMESTAMP,
|
|
34
|
+
UUID,
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::DBAdapter::DefaultSql
|
|
4
|
+
def create_schema_sql(schema, if_not_exists: false)
|
|
5
|
+
s = +"CREATE SCHEMA "
|
|
6
|
+
s << "IF NOT EXISTS " if if_not_exists
|
|
7
|
+
s << self.escape_identifier(schema.name)
|
|
8
|
+
return s
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create_table_sql(table, columns, if_not_exists: false)
|
|
12
|
+
createtable = +"CREATE TABLE "
|
|
13
|
+
createtable << "IF NOT EXISTS " if if_not_exists
|
|
14
|
+
createtable << self.qualify_table(table)
|
|
15
|
+
lines = ["#{createtable} ("]
|
|
16
|
+
columns[0...-1]&.each { |c| lines << " #{self.column_create_sql(c)}," }
|
|
17
|
+
lines << " #{self.column_create_sql(columns.last)}"
|
|
18
|
+
lines << ")"
|
|
19
|
+
return lines.join("\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def identifier_quote_char
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# We write our own escaper because we want to only escape what's needed;
|
|
27
|
+
# otherwise we want to avoid quoting identifiers.
|
|
28
|
+
def escape_identifier(s)
|
|
29
|
+
s = s.to_s
|
|
30
|
+
raise ArgumentError, "#{s} is an invalid identifier and should have been validated previously" unless
|
|
31
|
+
Webhookdb::DBAdapter::VALID_IDENTIFIER.match?(s)
|
|
32
|
+
|
|
33
|
+
quo = self.identifier_quote_char
|
|
34
|
+
return "#{quo}#{s}#{quo}" if RESERVED_KEYWORDS.include?(s.upcase) ||
|
|
35
|
+
s.include?(" ") ||
|
|
36
|
+
s.include?("-") ||
|
|
37
|
+
s.start_with?(/\d/)
|
|
38
|
+
return s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param [Webhookdb::DBAdapter::Table] table
|
|
42
|
+
def qualify_table(table)
|
|
43
|
+
s = +""
|
|
44
|
+
if table.schema
|
|
45
|
+
s << self.escape_identifier(table.schema.name)
|
|
46
|
+
s << "."
|
|
47
|
+
end
|
|
48
|
+
s << self.escape_identifier(table.name)
|
|
49
|
+
return s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Return the SQL string for column assignment.
|
|
53
|
+
# Like for src and dest of :src and :tgt,
|
|
54
|
+
# and columns with names :spam and :foo,
|
|
55
|
+
# return "tgt.spam = src.spam, tgt.foo = src.foo"
|
|
56
|
+
# Column names will be escaped; the source and destination values
|
|
57
|
+
# should already be valid identifiers (usually aliases for a table or query).
|
|
58
|
+
#
|
|
59
|
+
# If a block is given, call it with (column, left hand side string, right hand side string).
|
|
60
|
+
# It should return the new lhs/rhs strings.
|
|
61
|
+
#
|
|
62
|
+
# @param [String, nil] source Prefix (like table alias) for right hand side columns.
|
|
63
|
+
# @param [String, nil] destination nil Prefix (like table alias) for left hand side columns.
|
|
64
|
+
# @param [Array<Webhookdb::DBAdapter::Column>] columns
|
|
65
|
+
def assign_columns_sql(source, destination, columns, &block)
|
|
66
|
+
stmts = columns.map do |c|
|
|
67
|
+
cname = self.escape_identifier(c.name)
|
|
68
|
+
lhs = destination ? "#{destination}.#{cname}" : cname
|
|
69
|
+
rhs = source ? "#{source}.#{cname}" : cname
|
|
70
|
+
lhs, rhs = block[c, lhs, rhs] if block
|
|
71
|
+
"#{lhs} = #{rhs}"
|
|
72
|
+
end
|
|
73
|
+
return stmts.join(", ")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# These are all PG reserved keywords, as per https://www.postgresql.org/docs/current/sql-keywords-appendix.html
|
|
77
|
+
PG_RESERVED_KEYWORDS = Set.new(
|
|
78
|
+
[
|
|
79
|
+
"ALL",
|
|
80
|
+
"ANALYSE",
|
|
81
|
+
"ANALYZE",
|
|
82
|
+
"AND",
|
|
83
|
+
"ANY",
|
|
84
|
+
"ARRAY",
|
|
85
|
+
"AS",
|
|
86
|
+
"ASC",
|
|
87
|
+
"ASYMMETRIC",
|
|
88
|
+
"AUTHORIZATION",
|
|
89
|
+
"BINARY",
|
|
90
|
+
"BOTH",
|
|
91
|
+
"CASE",
|
|
92
|
+
"CAST",
|
|
93
|
+
"CHECK",
|
|
94
|
+
"COLLATE",
|
|
95
|
+
"COLLATION",
|
|
96
|
+
"COLUMN",
|
|
97
|
+
"CONCURRENTLY",
|
|
98
|
+
"CONSTRAINT",
|
|
99
|
+
"CREATE",
|
|
100
|
+
"CROSS",
|
|
101
|
+
"CURRENT_CATALOG",
|
|
102
|
+
"CURRENT_DATE",
|
|
103
|
+
"CURRENT_ROLE",
|
|
104
|
+
"CURRENT_SCHEMA",
|
|
105
|
+
"CURRENT_TIME",
|
|
106
|
+
"CURRENT_TIMESTAMP",
|
|
107
|
+
"CURRENT_USER",
|
|
108
|
+
"DECODE",
|
|
109
|
+
"DEFAULT",
|
|
110
|
+
"DEFERRABLE",
|
|
111
|
+
"DESC",
|
|
112
|
+
"DISTINCT",
|
|
113
|
+
"DISTRIBUTED",
|
|
114
|
+
"DO",
|
|
115
|
+
"ELSE",
|
|
116
|
+
"END",
|
|
117
|
+
"EXCEPT",
|
|
118
|
+
"FALSE",
|
|
119
|
+
"FETCH",
|
|
120
|
+
"FOR",
|
|
121
|
+
"FOREIGN",
|
|
122
|
+
"FREEZE",
|
|
123
|
+
"FROM",
|
|
124
|
+
"FULL",
|
|
125
|
+
"GRANT",
|
|
126
|
+
"GROUP",
|
|
127
|
+
"HAVING",
|
|
128
|
+
"ILIKE",
|
|
129
|
+
"IN",
|
|
130
|
+
"INITIALLY",
|
|
131
|
+
"INNER",
|
|
132
|
+
"INTERSECT",
|
|
133
|
+
"INTO",
|
|
134
|
+
"IS",
|
|
135
|
+
"ISNULL",
|
|
136
|
+
"JOIN",
|
|
137
|
+
"LATERAL",
|
|
138
|
+
"LEADING",
|
|
139
|
+
"LEFT",
|
|
140
|
+
"LIKE",
|
|
141
|
+
"LIMIT",
|
|
142
|
+
"LOCALTIME",
|
|
143
|
+
"LOCALTIMESTAMP",
|
|
144
|
+
"LOG",
|
|
145
|
+
"NATURAL",
|
|
146
|
+
"NOT",
|
|
147
|
+
"NOTNULL",
|
|
148
|
+
"NULL",
|
|
149
|
+
"OFFSET",
|
|
150
|
+
"ON",
|
|
151
|
+
"ONLY",
|
|
152
|
+
"OR",
|
|
153
|
+
"ORDER",
|
|
154
|
+
"OUTER",
|
|
155
|
+
"OVERLAPS",
|
|
156
|
+
"PLACING",
|
|
157
|
+
"PRIMARY",
|
|
158
|
+
"REFERENCES",
|
|
159
|
+
"RETURNING",
|
|
160
|
+
"RIGHT",
|
|
161
|
+
"SCATTER",
|
|
162
|
+
"SELECT",
|
|
163
|
+
"SESSION_USER",
|
|
164
|
+
"SIMILAR",
|
|
165
|
+
"SOME",
|
|
166
|
+
"SYMMETRIC",
|
|
167
|
+
"TABLE",
|
|
168
|
+
"THEN",
|
|
169
|
+
"TO",
|
|
170
|
+
"TRAILING",
|
|
171
|
+
"TRUE",
|
|
172
|
+
"UNION",
|
|
173
|
+
"UNIQUE",
|
|
174
|
+
"USER",
|
|
175
|
+
"USING",
|
|
176
|
+
"VARIADIC",
|
|
177
|
+
"VERBOSE",
|
|
178
|
+
"WHEN",
|
|
179
|
+
"WHERE",
|
|
180
|
+
"WINDOW",
|
|
181
|
+
"WITH",
|
|
182
|
+
],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Reserved keywords must be quoted to be used as identifiers.
|
|
186
|
+
RESERVED_KEYWORDS = PG_RESERVED_KEYWORDS
|
|
187
|
+
end
|