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,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
|
|
6
|
+
module Webhookdb::Plivo
|
|
7
|
+
include Appydays::Configurable
|
|
8
|
+
include Appydays::Loggable
|
|
9
|
+
|
|
10
|
+
configurable(:plivo) do
|
|
11
|
+
setting :http_timeout, 25
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.request(method, tail, auth_id:, auth_token:, body: nil, **options)
|
|
15
|
+
tail = tail.delete_suffix("/")
|
|
16
|
+
url = "https://api.plivo.com/v1/Account/#{auth_id}#{tail}/"
|
|
17
|
+
options[:basic_auth] = {username: auth_id, password: auth_token}
|
|
18
|
+
options[:logger] = self.logger
|
|
19
|
+
if body
|
|
20
|
+
options[:headers] = {"Content-Type" => "application/json"}
|
|
21
|
+
options[:body] = body.to_json
|
|
22
|
+
end
|
|
23
|
+
options[:method] = method if method != :get
|
|
24
|
+
return Webhookdb::Http.send(method, url, **options)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.webhook_response(request, auth_token)
|
|
28
|
+
raise Webhookdb::InvalidPrecondition, "auth_token cannot be nil/blank" if auth_token.blank?
|
|
29
|
+
# See https://www.plivo.com/docs/sms/xml/request#validation
|
|
30
|
+
# See https://www.plivo.com/docs/sms/concepts/signature-validation#code
|
|
31
|
+
(signature = request.env["HTTP_X_PLIVO_SIGNATURE_V2"]) or
|
|
32
|
+
return Webhookdb::WebhookResponse.error("missing signature")
|
|
33
|
+
(nonce = request.env["HTTP_X_PLIVO_SIGNATURE_V2_NONCE"]) or
|
|
34
|
+
return Webhookdb::WebhookResponse.error("missing nonce")
|
|
35
|
+
url = request.url
|
|
36
|
+
uri = url.split("?")[0]
|
|
37
|
+
ok = self._valid_signature?(uri, nonce, signature, auth_token)
|
|
38
|
+
return ok ? Webhookdb::WebhookResponse.ok : Webhookdb::WebhookResponse.error("invalid signature")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Copied from https://github.com/plivo/plivo-ruby/blob/119038345475c6216bf040926747105b66fd588a/lib/plivo/utils.rb#L213C1-L220C8
|
|
42
|
+
# We do not use the Plivo gem since it is a mess.
|
|
43
|
+
def self._valid_signature?(uri, nonce, signature, auth_token)
|
|
44
|
+
parsed_uri = URI.parse(uri)
|
|
45
|
+
uri_details = {host: parsed_uri.host, path: parsed_uri.path}
|
|
46
|
+
uri_builder_module = parsed_uri.scheme == "https" ? URI::HTTPS : URI::HTTP
|
|
47
|
+
data_to_sign = uri_builder_module.build(uri_details).to_s + nonce
|
|
48
|
+
sha256_digest = OpenSSL::Digest.new("sha256")
|
|
49
|
+
encoded_digest = Base64.encode64(OpenSSL::HMAC.digest(sha256_digest, auth_token, data_to_sign)).strip
|
|
50
|
+
return ActiveSupport::SecurityUtils.secure_compare(encoded_digest, signature)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::Postgres::Maintenance
|
|
4
|
+
include Appydays::Configurable
|
|
5
|
+
|
|
6
|
+
configurable(:pg_maintenance) do
|
|
7
|
+
setting :debug, true
|
|
8
|
+
setting :docker, true
|
|
9
|
+
# Match what's available on the server.
|
|
10
|
+
setting :pg_repack_image, "hartmutcouk/pg-repack-docker:1.4.7"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Base
|
|
14
|
+
# @!attribute service_integration
|
|
15
|
+
# @return [Webhookdb::ServiceIntegration]
|
|
16
|
+
attr_reader :service_integration
|
|
17
|
+
|
|
18
|
+
def debug? = Webhookdb::Postgres::Maintenance.debug
|
|
19
|
+
|
|
20
|
+
def initialize(service_integration)
|
|
21
|
+
@service_integration = service_integration
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def conn_params
|
|
25
|
+
org_uri = URI(self.service_integration.organization.admin_connection_url_raw)
|
|
26
|
+
superuser_url = Webhookdb::Organization::DbBuilder.available_server_urls.find { |u| URI(u).host == org_uri.host }
|
|
27
|
+
raise Webhookdb::InvalidPrecondition, "cannot find superuser url for #{org_uri.host}" if superuser_url.nil?
|
|
28
|
+
sup_uri = URI(superuser_url)
|
|
29
|
+
return {
|
|
30
|
+
user: sup_uri.user,
|
|
31
|
+
password: sup_uri.password,
|
|
32
|
+
host: sup_uri.host,
|
|
33
|
+
port: sup_uri.port,
|
|
34
|
+
database: org_uri.path&.delete_prefix("/"),
|
|
35
|
+
table: self.service_integration.table_name,
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Query < Base
|
|
41
|
+
def connstr
|
|
42
|
+
h = self.conn_params
|
|
43
|
+
return "postgres://#{h[:user]}:#{h[:password]}@#{h[:host]}:#{h[:port]}/#{h[:database]}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def query = raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
def fetch
|
|
49
|
+
Sequel.connect(self.connstr) do |c|
|
|
50
|
+
c.fetch(self.query).all
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def run = raise NotImplementedError
|
|
55
|
+
def run_fmt = raise NotImplementedError
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Command < Base
|
|
59
|
+
def docker? = Webhookdb::Postgres::Maintenance.docker
|
|
60
|
+
|
|
61
|
+
def psql_conn_params
|
|
62
|
+
h = self.conn_params
|
|
63
|
+
return [
|
|
64
|
+
"--no-password",
|
|
65
|
+
"-U", h[:user],
|
|
66
|
+
"-h", h[:host],
|
|
67
|
+
"-p", h[:port],
|
|
68
|
+
"--dbname", h[:database],
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def command_list = raise NotImplementedError
|
|
73
|
+
def docker_image = raise NotImplementedError
|
|
74
|
+
def extension = nil
|
|
75
|
+
|
|
76
|
+
def docker_preamble
|
|
77
|
+
return [
|
|
78
|
+
"docker",
|
|
79
|
+
"run",
|
|
80
|
+
"-e",
|
|
81
|
+
"PGPASSWORD=#{self.conn_params[:password]}",
|
|
82
|
+
"-it",
|
|
83
|
+
"--rm",
|
|
84
|
+
self.docker_image,
|
|
85
|
+
]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_extension_command_list
|
|
89
|
+
a = ["PGPASSWORD=#{self.conn_params[:password]}", "psql"]
|
|
90
|
+
a += self.psql_conn_params
|
|
91
|
+
a << "-c"
|
|
92
|
+
a << "'CREATE EXTENSION IF NOT EXISTS #{self.extension}'"
|
|
93
|
+
return a
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def command_strings
|
|
97
|
+
c = []
|
|
98
|
+
c << self.shelex(self.create_extension_command_list) if self.extension
|
|
99
|
+
c << self.shelex(self.command_list)
|
|
100
|
+
return c
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def shelex(a)
|
|
104
|
+
return a.join(" ")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Repack < Command
|
|
109
|
+
def extension = "pg_repack"
|
|
110
|
+
|
|
111
|
+
def docker_image = Webhookdb::Postgres::Maintenance.pg_repack_image
|
|
112
|
+
|
|
113
|
+
def command_list
|
|
114
|
+
c = []
|
|
115
|
+
c += self.docker_preamble if self.docker?
|
|
116
|
+
c << "pg_repack"
|
|
117
|
+
c += self.psql_conn_params
|
|
118
|
+
c += ["--table", self.conn_params[:table]]
|
|
119
|
+
c << "--no-superuser-check"
|
|
120
|
+
c << "--dry-run"
|
|
121
|
+
c += ["--echo", "--elevel=DEBUG"] if self.debug?
|
|
122
|
+
return c
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class Count < Query
|
|
127
|
+
def query = "SELECT reltuples AS estimate FROM pg_class WHERE relname = '#{self.service_integration.table_name}'"
|
|
128
|
+
|
|
129
|
+
def run
|
|
130
|
+
r = self.fetch
|
|
131
|
+
return r[0][:estimate].to_i
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def run_fmt = ActiveSupport::NumberHelper.number_to_delimited(self.run.to_i)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class Tables < Query
|
|
138
|
+
def query
|
|
139
|
+
return <<~SQL
|
|
140
|
+
SELECT
|
|
141
|
+
relname AS "relation",
|
|
142
|
+
reltuples as "tuples",
|
|
143
|
+
pg_size_pretty(pg_total_relation_size(C .oid)) as "size"
|
|
144
|
+
FROM pg_class C
|
|
145
|
+
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
|
146
|
+
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
|
147
|
+
AND C.relkind = 'r'
|
|
148
|
+
AND nspname !~ '^pg_toast'
|
|
149
|
+
ORDER BY C.relname
|
|
150
|
+
SQL
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def run
|
|
154
|
+
return self.fetch
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def run_fmt
|
|
158
|
+
rows = self.run
|
|
159
|
+
namejust = rows.map { |r| r[:relation].size }.max
|
|
160
|
+
return rows.map do |r|
|
|
161
|
+
tuples = ActiveSupport::NumberHelper.number_to_delimited(r[:tuples].to_i).rjust(12, " ")
|
|
162
|
+
"#{r[:relation].ljust(namejust + 1, ' ')} #{r[:size].ljust(7, ' ')} #{tuples}"
|
|
163
|
+
end.join("\n")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
require "pg"
|
|
6
|
+
require "sequel"
|
|
7
|
+
require "tsort"
|
|
8
|
+
|
|
9
|
+
require "webhookdb"
|
|
10
|
+
require "webhookdb/postgres"
|
|
11
|
+
require "webhookdb/postgres/validations"
|
|
12
|
+
require "webhookdb/postgres/model_utilities"
|
|
13
|
+
|
|
14
|
+
# Initialize the Webhookdb::Postgres::Model class as an abstract model class (i.e.,
|
|
15
|
+
# without a default dataset). This prevents it from looking for a table called
|
|
16
|
+
# `models`, and makes inheriting it more straightforward.
|
|
17
|
+
# Thanks to Michael Granger and Jeremy Evans for the suggestion.
|
|
18
|
+
Webhookdb::Postgres::Model = Class.new(Sequel::Model)
|
|
19
|
+
Webhookdb::Postgres::Model.def_Model(Webhookdb::Postgres)
|
|
20
|
+
|
|
21
|
+
class Webhookdb::Postgres::Model
|
|
22
|
+
include Appydays::Configurable
|
|
23
|
+
extend Webhookdb::Postgres::ModelUtilities
|
|
24
|
+
include Appydays::Loggable
|
|
25
|
+
|
|
26
|
+
configurable(:webhookdb_db) do
|
|
27
|
+
setting :uri, "postgres:/webhookdb_test", key: "DATABASE_URL"
|
|
28
|
+
|
|
29
|
+
# rubocop:disable Naming/VariableNumber
|
|
30
|
+
setting :encryption_key_0, "Tc3X6zkxXgZfHE81MFz2EILStV++BuQY"
|
|
31
|
+
# rubocop:enable Naming/VariableNumber
|
|
32
|
+
|
|
33
|
+
setting :extension_schema, "public"
|
|
34
|
+
|
|
35
|
+
after_configured do
|
|
36
|
+
self.connect(self.uri)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Add one or more extension +modules+ to the receiving class. This allows subsystems
|
|
41
|
+
# like Orders, etc. to decorate models outside of their purview
|
|
42
|
+
# without introducing unnecessary dependencies.
|
|
43
|
+
#
|
|
44
|
+
# Each one of the given +modules+ will be included in the receiving model class, and
|
|
45
|
+
# if it also contains a constant called ClassMethods, the model class will be
|
|
46
|
+
# also be extended with it.
|
|
47
|
+
#
|
|
48
|
+
# @example Add order methods to Webhookdb::Customer
|
|
49
|
+
#
|
|
50
|
+
# module Webhookdb::Orders::CustomerExtensions
|
|
51
|
+
#
|
|
52
|
+
# # Add some associations for Order models
|
|
53
|
+
# def included( model )
|
|
54
|
+
# super
|
|
55
|
+
# model.one_to_many :orders, Sequel[:app][:orders]
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# def first_order
|
|
59
|
+
# self.orders.first
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# end
|
|
63
|
+
def self.add_extensions(*modules)
|
|
64
|
+
self.logger.info "Adding extensions to %p: %p" % [self, modules]
|
|
65
|
+
|
|
66
|
+
modules.each do |mod|
|
|
67
|
+
include(mod)
|
|
68
|
+
if mod.const_defined?(:ClassMethods)
|
|
69
|
+
submod = mod.const_get(:ClassMethods)
|
|
70
|
+
self.extend(submod)
|
|
71
|
+
end
|
|
72
|
+
if mod.const_defined?(:PrependedMethods)
|
|
73
|
+
submod = mod.const_get(:PrependedMethods)
|
|
74
|
+
prepend(submod)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
plugin :column_encryption do |enc|
|
|
80
|
+
enc.key 0, self.encryption_key_0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tsort"
|
|
4
|
+
require "pg"
|
|
5
|
+
require "webhookdb/dbutil"
|
|
6
|
+
require "webhookdb/postgres"
|
|
7
|
+
|
|
8
|
+
# A collection of utilities that can be added to model superclasses.
|
|
9
|
+
module Webhookdb::Postgres::ModelUtilities
|
|
10
|
+
include TSort
|
|
11
|
+
|
|
12
|
+
# Extension callback -- register the +model_class+ with Webhookdb::Postgres.
|
|
13
|
+
def self.extended(model_class)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
# Sequel::Model API -- load some plugins
|
|
17
|
+
model_class.plugin(:dirty)
|
|
18
|
+
model_class.plugin(:json_serializer)
|
|
19
|
+
model_class.plugin(:many_through_many)
|
|
20
|
+
model_class.plugin(:subclasses)
|
|
21
|
+
model_class.plugin(:tactical_eager_loading)
|
|
22
|
+
model_class.plugin(:update_or_create)
|
|
23
|
+
model_class.plugin(:validation_helpers)
|
|
24
|
+
|
|
25
|
+
model_class.include(Appydays::Loggable)
|
|
26
|
+
model_class.extend(ClassMethods)
|
|
27
|
+
model_class.include(InstanceMethods)
|
|
28
|
+
model_class.dataset_module(DatasetMethods)
|
|
29
|
+
model_class.include(Webhookdb::Postgres::Validations)
|
|
30
|
+
|
|
31
|
+
Webhookdb::Postgres.register_model_superclass(model_class)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module ClassMethods
|
|
35
|
+
# The application name, set on database connections.
|
|
36
|
+
attr_reader :appname
|
|
37
|
+
|
|
38
|
+
# Connect to the given URI using the standard extensions and other options.
|
|
39
|
+
def connect(uri)
|
|
40
|
+
newdb = Sequel.connect(
|
|
41
|
+
uri,
|
|
42
|
+
logger: self.logger,
|
|
43
|
+
extensions: [
|
|
44
|
+
:is_distinct_from,
|
|
45
|
+
:pagination,
|
|
46
|
+
:pg_json,
|
|
47
|
+
:pg_inet,
|
|
48
|
+
:pg_array,
|
|
49
|
+
:pg_streaming,
|
|
50
|
+
:pg_range,
|
|
51
|
+
:pg_interval,
|
|
52
|
+
:pg_triggers,
|
|
53
|
+
:pretty_table,
|
|
54
|
+
],
|
|
55
|
+
**Webhookdb::Dbutil.configured_connection_options,
|
|
56
|
+
)
|
|
57
|
+
self.db = newdb
|
|
58
|
+
self.descendents.each do |subclass|
|
|
59
|
+
subclass.db = newdb
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Set the PostgreSQL application name to +name+ to allow per-application connection
|
|
64
|
+
# tracking and other fun stuff.
|
|
65
|
+
def appname=(name)
|
|
66
|
+
@appname = name
|
|
67
|
+
self.update_connection_appname
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Set the connection's application name if there is one.
|
|
71
|
+
def update_connection_appname
|
|
72
|
+
return unless self.db
|
|
73
|
+
self.logger.debug "Setting application name to %p" % [self.appname]
|
|
74
|
+
self.db.synchronize do |conn|
|
|
75
|
+
escaped = conn.escape_string(self.appname)
|
|
76
|
+
conn.exec("SET application_name TO '%s'" % [escaped])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Fetch a model class by its +classname+. This can be the fully-qualified
|
|
81
|
+
# name, or just the bit after 'Webhookdb::'.
|
|
82
|
+
def by_name(classname)
|
|
83
|
+
return self.descendents.find do |cl|
|
|
84
|
+
cl.name&.end_with?(classname)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Return the Array of the schemas used by all descendents of the receiving
|
|
89
|
+
# model class.
|
|
90
|
+
def all_loaded_schemas
|
|
91
|
+
return self.descendents.map(&:schema_name).uniq.compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Create a new schema named +name+ (if it doesn't already exist).
|
|
95
|
+
def create_schema(name, &block)
|
|
96
|
+
self.db.create_schema(name, if_not_exists: true)
|
|
97
|
+
self.instance_eval(&block) if block
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Create the schema named +name+, dropping any previous schema by the same name.
|
|
101
|
+
def create_schema!(name, &)
|
|
102
|
+
self.drop_schema!(name)
|
|
103
|
+
self.create_schema(name, &)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Drop the empty schema named +name+ (if it exists).
|
|
107
|
+
def drop_schema(name)
|
|
108
|
+
self.db.drop_schema(name, if_exists: true)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Drop the schema named +name+ and all of its tables.
|
|
112
|
+
def drop_schema!(name)
|
|
113
|
+
self.db.drop_schema(name, if_exists: true, cascade: true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns +true+ if a schema named +name+ exists.
|
|
117
|
+
def schema_exists?(name=self.schema_name)
|
|
118
|
+
ds = self.db[Sequel[:pg_catalog][:pg_namespace]].
|
|
119
|
+
filter(nspname: name.to_s).
|
|
120
|
+
select(:nspname)
|
|
121
|
+
|
|
122
|
+
return ds.first ? true : false
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Return the name of the schema the receiving class is in.
|
|
126
|
+
def schema_name
|
|
127
|
+
schemaname, = self.db.send(:schema_and_table, self.table_name)
|
|
128
|
+
return schemaname
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def now_sql
|
|
132
|
+
return Webhookdb::Postgres.now_sql
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def extension_schema
|
|
136
|
+
return "public"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def install_all_extensions
|
|
140
|
+
extensions = [
|
|
141
|
+
"citext",
|
|
142
|
+
"pgcrypto",
|
|
143
|
+
"btree_gist",
|
|
144
|
+
"pg_trgm",
|
|
145
|
+
]
|
|
146
|
+
extensions.each do |ext|
|
|
147
|
+
self.db.execute("CREATE EXTENSION IF NOT EXISTS #{ext} WITH SCHEMA #{self.extension_schema}")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# TSort API -- yield each model class.
|
|
152
|
+
def tsort_each_node(&)
|
|
153
|
+
self.descendents.select(&:name).each(&)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# TSort API -- yield each of the given +model_class+'s dependent model
|
|
157
|
+
# classes.
|
|
158
|
+
def tsort_each_child(model_class)
|
|
159
|
+
# Include (non-anonymous) parents other than Model
|
|
160
|
+
non_anon_parents = model_class.ancestors[1..].
|
|
161
|
+
select { |cl| cl < self }.
|
|
162
|
+
select(&:name)
|
|
163
|
+
# rubocop:disable Style/ExplicitBlockArgument
|
|
164
|
+
non_anon_parents.each do |parentclass|
|
|
165
|
+
yield(parentclass)
|
|
166
|
+
end
|
|
167
|
+
# rubocop:enable Style/ExplicitBlockArgument
|
|
168
|
+
|
|
169
|
+
# Include associated classes for which this model class's table has a
|
|
170
|
+
# foreign key
|
|
171
|
+
model_class.association_reflections.each do |name, config|
|
|
172
|
+
next if config[:polymorphic]
|
|
173
|
+
|
|
174
|
+
associated_class = Object.const_get(config[:class_name])
|
|
175
|
+
|
|
176
|
+
if config[:type] == :many_to_one
|
|
177
|
+
self.logger.debug " %p#%s is dependent on %p" %
|
|
178
|
+
[model_class, name, associated_class]
|
|
179
|
+
yield(associated_class)
|
|
180
|
+
else
|
|
181
|
+
self.logger.debug " %p#%s is *not* dependent on %p" %
|
|
182
|
+
[model_class, name, associated_class]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Like +find_or_create+, but will +find+ again if the +create+
|
|
189
|
+
# call fails due to a +Sequel::UniqueConstraintViolation+,
|
|
190
|
+
# which is usually caused by a race condition.
|
|
191
|
+
def find_or_create_or_find(params, &)
|
|
192
|
+
# Set a savepoint, because the DB error will abort the current transaction.
|
|
193
|
+
self.db.transaction(savepoint: true) do
|
|
194
|
+
return self.find_or_create(params, &)
|
|
195
|
+
end
|
|
196
|
+
rescue Sequel::UniqueConstraintViolation
|
|
197
|
+
return self.find(params)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
module InstanceMethods
|
|
201
|
+
# Return a human-readable representation of the object as a String suitable for debugging.
|
|
202
|
+
def inspect
|
|
203
|
+
values = self.values.reject do |k, v|
|
|
204
|
+
v.blank? || k.to_s.end_with?("_currency")
|
|
205
|
+
end
|
|
206
|
+
begin
|
|
207
|
+
encrypted = self.class.send(:column_encryption_metadata).to_set { |(col, _)| col.to_s }
|
|
208
|
+
rescue NoMethodError
|
|
209
|
+
encrypted = Set.new
|
|
210
|
+
end
|
|
211
|
+
values = values.map do |(k, v)|
|
|
212
|
+
k = k.to_s
|
|
213
|
+
v = if v.is_a?(Time)
|
|
214
|
+
self.inspect_time(v)
|
|
215
|
+
elsif v.respond_to?(:db_type) && v.db_type.to_s == "tstzrange"
|
|
216
|
+
"%s%s...%s%s" % [
|
|
217
|
+
v.exclude_begin? ? "(" : "[",
|
|
218
|
+
v.begin ? self.inspect_time(v.begin) : "nil",
|
|
219
|
+
v.end ? self.inspect_time(v.end) : "nil",
|
|
220
|
+
v.exclude_end? ? ")" : "]",
|
|
221
|
+
]
|
|
222
|
+
elsif k.end_with?("_cents")
|
|
223
|
+
accessor = k.match(/^([a-z_]+)_cents/)[1]
|
|
224
|
+
k = accessor
|
|
225
|
+
self.send(accessor).format
|
|
226
|
+
elsif encrypted.include?(k)
|
|
227
|
+
# Render encrypted fields as xyz...abc, or if a URL, hide the user/password.
|
|
228
|
+
unenc = self.send(k)
|
|
229
|
+
if unenc.length < 10
|
|
230
|
+
"\"...\""
|
|
231
|
+
elsif unenc.include?("://")
|
|
232
|
+
uri = URI(unenc)
|
|
233
|
+
uri.user = "*"
|
|
234
|
+
uri.password = "*"
|
|
235
|
+
uri.to_s.inspect
|
|
236
|
+
else
|
|
237
|
+
"\"#{unenc[..2]}...#{unenc[-3..]}\""
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
v.inspect
|
|
241
|
+
end
|
|
242
|
+
"#{k}: #{v}"
|
|
243
|
+
end
|
|
244
|
+
return "#<%p %s>" % [self.class, values.join(", ")]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def inspect_time(t)
|
|
248
|
+
return t.in_time_zone(Time.zone).strftime("%Y-%m-%d %H:%M:%S")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Return the objects validation errors as full messages joined with commas.
|
|
252
|
+
def error_messages
|
|
253
|
+
return self.errors.full_messages.join(", ")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Return the string used as a topic for events sent from the receiving object.
|
|
257
|
+
def event_prefix
|
|
258
|
+
prefix = self.class.name or return # No events for anonymous classes
|
|
259
|
+
return prefix.gsub("::", ".").downcase
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Publish an event from the receiving object of the specified +type+ and with the given +payload+.
|
|
263
|
+
# This does *not* wait for the transaction to complete, so subscribers may not be able to observe
|
|
264
|
+
# any model changes in the database. You probably want to use published_deferred.
|
|
265
|
+
def publish_immediate(type, *payload)
|
|
266
|
+
prefix = self.event_prefix or return
|
|
267
|
+
Amigo.publish(prefix + "." + type.to_s, *payload)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Publish an event in the current db's/transaction's +after_commit+ hook.
|
|
271
|
+
def publish_deferred(type, *payload)
|
|
272
|
+
Webhookdb::Postgres.defer_after_commit(self.db) do
|
|
273
|
+
self.publish_immediate(type, *payload)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Take an exclusive lock on the receiver, ensuring nothing else has updated the object in the meantime.
|
|
278
|
+
# If the updated_at changed from what's on the receiver, to after it acquired the lock, raise LockFailed.
|
|
279
|
+
# Save changes and touch updated_at after calling the given block.
|
|
280
|
+
def resource_lock!
|
|
281
|
+
self.db.transaction do
|
|
282
|
+
old_updated = self.round_time(self.updated_at)
|
|
283
|
+
self.lock!
|
|
284
|
+
new_updated = self.round_time(self.updated_at)
|
|
285
|
+
raise Webhookdb::LockFailed if old_updated != new_updated
|
|
286
|
+
result = yield(self)
|
|
287
|
+
self.updated_at = Time.now
|
|
288
|
+
self.save_changes
|
|
289
|
+
return result
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Round +Time+ t to remove nanoseconds, since Postgres can only store microseconds.
|
|
294
|
+
protected def round_time(t)
|
|
295
|
+
return nil if t.nil?
|
|
296
|
+
return t.change(nsec: t.usec * 1000)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
protected def now_sql
|
|
300
|
+
return Webhookdb::Postgres.now_sql
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
protected def prep_amigo_payload(data)
|
|
304
|
+
# Job arguments to Amigo::Router must be native JSON types, so we do some preprocessing
|
|
305
|
+
data.to_h do |key, value|
|
|
306
|
+
# Empty PG Ranges cannot be converted to ruby types for some reason, we substitute nil
|
|
307
|
+
value_is_empty_range = value.is_a?(Sequel::Postgres::PGRange) && value.empty?
|
|
308
|
+
new_value = value_is_empty_range ? nil : value.as_json
|
|
309
|
+
[key, new_value]
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Sequel hook -- send an asynchronous event after the model is saved.
|
|
314
|
+
def after_create
|
|
315
|
+
super
|
|
316
|
+
self.publish_deferred("created", self.id, prep_amigo_payload(self.values))
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Sequel hook -- send an asynchronous event after the save is committed.
|
|
320
|
+
def after_update
|
|
321
|
+
super
|
|
322
|
+
self.publish_deferred("updated", self.id, prep_amigo_payload(self.previous_changes))
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Sequel hook -- send an event after a transaction that destroys the object is committed.
|
|
326
|
+
def after_destroy
|
|
327
|
+
super
|
|
328
|
+
self.publish_deferred("destroyed", self.id, prep_amigo_payload(self.values))
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
module DatasetMethods
|
|
333
|
+
# Helper for applying multiple conditions for Sequel, where some can be nil.
|
|
334
|
+
def reduce_expr(op_symbol, operands, method: :where)
|
|
335
|
+
return Webhookdb::Dbutil.reduce_expr(self, op_symbol, operands, method:)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Call a block for each row in a dataset.
|
|
339
|
+
# This is the same as paged_each or use_cursor.each, except that for each page,
|
|
340
|
+
# rows are re-fetched using self.where(primary_key => [pks]).all to enable eager loading.
|
|
341
|
+
#
|
|
342
|
+
# (Note that paged_each does not do eager loading, which makes enumerating model associations very slow)
|
|
343
|
+
def each_cursor_page(page_size: 500, order: :id, &block)
|
|
344
|
+
raise LocalJumpError unless block
|
|
345
|
+
raise "dataset requires a use_cursor method, class may need `extension(:pagination)`" unless
|
|
346
|
+
self.respond_to?(:use_cursor)
|
|
347
|
+
model = self.model
|
|
348
|
+
pk = model.primary_key
|
|
349
|
+
current_chunk_pks = []
|
|
350
|
+
order = [order] unless order.respond_to?(:to_ary)
|
|
351
|
+
self.naked.select(pk).order(*order).use_cursor(rows_per_fetch: page_size, hold: true).each do |row|
|
|
352
|
+
current_chunk_pks << row[pk]
|
|
353
|
+
next if current_chunk_pks.length < page_size
|
|
354
|
+
page = model.where(pk => current_chunk_pks).order(*order).all
|
|
355
|
+
current_chunk_pks.clear
|
|
356
|
+
page.each(&block)
|
|
357
|
+
end
|
|
358
|
+
model.where(pk => current_chunk_pks).order(*order).all.each(&block)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# See each_cursor_page, but takes an additional action on each chunk of returned rows.
|
|
362
|
+
# The action is called with pages of return values from the block when a page is is reached.
|
|
363
|
+
# Each call to action should return nil, a result, or an array of results (nil results are ignored).
|
|
364
|
+
#
|
|
365
|
+
# The most common case is for ETL: process one dataset, map it in a block to return new row values,
|
|
366
|
+
# and multi_insert it into a different table.
|
|
367
|
+
def each_cursor_page_action(action:, page_size: 500, order: :id)
|
|
368
|
+
raise LocalJumpError unless block_given?
|
|
369
|
+
returned_rows_chunk = []
|
|
370
|
+
self.each_cursor_page(page_size:, order:) do |instance|
|
|
371
|
+
new_row = yield(instance)
|
|
372
|
+
next if action.nil? || new_row.nil?
|
|
373
|
+
new_row.respond_to?(:to_ary) ? returned_rows_chunk.concat(new_row) : returned_rows_chunk.push(new_row)
|
|
374
|
+
if returned_rows_chunk.length >= page_size
|
|
375
|
+
action.call(returned_rows_chunk)
|
|
376
|
+
returned_rows_chunk.clear
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
action&.call(returned_rows_chunk)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
require "sequel/plugins/money_fields"
|
|
5
|
+
require "sequel/plugins/tstzrange_fields"
|
|
6
|
+
|
|
7
|
+
# A placeholder model for the magical mixin classes (imaged, ticketable, annotated).
|
|
8
|
+
# We can't easily test these things with non-Webhookdb::Postgres::Model subclasses,
|
|
9
|
+
# and we can't easily define Webhookdb::Postgres::Model subclasses as part of a spec/transaction.
|
|
10
|
+
# Even if Sequel can handle it, it creates some global state issues.
|
|
11
|
+
# Make sure this class is only required during specs, and not normal gem usage.
|
|
12
|
+
# It should never have a persistent table; only a table in the test database.
|
|
13
|
+
class Webhookdb::Postgres::TestingPixie < Webhookdb::Postgres::Model(:testing_pixies)
|
|
14
|
+
plugin :money_fields, :price_per_unit
|
|
15
|
+
plugin :tstzrange_fields, :active_during
|
|
16
|
+
end
|