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,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/postgres/model"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/message"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::Message::Delivery < Webhookdb::Postgres::Model(:message_deliveries)
|
|
8
|
+
plugin :timestamps
|
|
9
|
+
plugin :soft_deletes
|
|
10
|
+
|
|
11
|
+
many_to_one :recipient, class: "Webhookdb::Customer"
|
|
12
|
+
one_to_many :bodies, class: "Webhookdb::Message::Body"
|
|
13
|
+
|
|
14
|
+
dataset_module do
|
|
15
|
+
def unsent
|
|
16
|
+
return self.not_soft_deleted.where(sent_at: nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sent
|
|
20
|
+
return self.not_soft_deleted.exclude(sent_at: nil)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_customers(customers)
|
|
24
|
+
emails = customers.is_a?(Sequel::Dataset) ? customers.select(:email) : customers.map(&:email)
|
|
25
|
+
return self.where(Sequel[to: emails] | Sequel[recipient: customers])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(*)
|
|
30
|
+
super
|
|
31
|
+
self[:extra_fields] ||= {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def body_with_mediatype(mt)
|
|
35
|
+
return self.bodies.find { |b| b.mediatype == mt }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def body_with_mediatype!(mt)
|
|
39
|
+
(b = self.body_with_mediatype(mt)) or raise "Delivery #{self.id} has no body with mediatype #{mt}"
|
|
40
|
+
return b
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def sent?
|
|
44
|
+
return self.sent_at ? true : false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def send!
|
|
48
|
+
return nil if self.sent? || self.soft_deleted?
|
|
49
|
+
self.db.transaction do
|
|
50
|
+
self.lock!
|
|
51
|
+
return nil if self.sent? || self.soft_deleted?
|
|
52
|
+
begin
|
|
53
|
+
transport_message_id = self.transport.send!(self)
|
|
54
|
+
rescue Webhookdb::Message::Transport::UndeliverableRecipient
|
|
55
|
+
self.logger.warn("undeliverable_recipient", recipient: self.to, message_delivery_id: self.id)
|
|
56
|
+
self.soft_delete
|
|
57
|
+
return self
|
|
58
|
+
end
|
|
59
|
+
if transport_message_id.blank?
|
|
60
|
+
self.logger.error("empty_transport_message_id", message_delivery_id: self.id)
|
|
61
|
+
transport_message_id = "WARNING-NOT-SET"
|
|
62
|
+
end
|
|
63
|
+
self.update(transport_message_id:, sent_at: Time.now)
|
|
64
|
+
return self
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def transport
|
|
69
|
+
return Webhookdb::Message::Transport.for(self.transport_type)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def transport!
|
|
73
|
+
return Webhookdb::Message::Transport.for!(self.transport_type)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.preview(template_class_name, transport: :email, rack_env: Webhookdb::RACK_ENV, commit: false)
|
|
77
|
+
raise "Can only preview in development or commit:false" unless rack_env == "development" || !commit
|
|
78
|
+
|
|
79
|
+
Gem.find_files("webhookdb/messages/**/*.rb").each do |path|
|
|
80
|
+
require path
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
template_class = "Webhookdb::Messages::#{template_class_name}".constantize
|
|
85
|
+
rescue NameError
|
|
86
|
+
raise Webhookdb::Message::MissingTemplateError, "Webhookdb::Messages::#{template_class_name} not found"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
require "webhookdb/fixtures"
|
|
90
|
+
Webhookdb::Fixtures.load_all
|
|
91
|
+
|
|
92
|
+
delivery = nil
|
|
93
|
+
self.db.transaction(rollback: commit ? nil : :always) do
|
|
94
|
+
to = Webhookdb::Fixtures.customer.create
|
|
95
|
+
template = template_class.fixtured(to)
|
|
96
|
+
delivery = template.dispatch(to, transport:)
|
|
97
|
+
delivery.bodies # Fetch this ahead of time so it is there after rollback
|
|
98
|
+
end
|
|
99
|
+
return delivery
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Table: message_deliveries
|
|
104
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
|
105
|
+
# Columns:
|
|
106
|
+
# id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
|
|
107
|
+
# created_at | timestamp with time zone | NOT NULL DEFAULT now()
|
|
108
|
+
# updated_at | timestamp with time zone |
|
|
109
|
+
# template | text | NOT NULL
|
|
110
|
+
# transport_type | text | NOT NULL
|
|
111
|
+
# transport_service | text | NOT NULL
|
|
112
|
+
# transport_message_id | text |
|
|
113
|
+
# sent_at | timestamp with time zone |
|
|
114
|
+
# to | text | NOT NULL
|
|
115
|
+
# recipient_id | integer |
|
|
116
|
+
# extra_fields | jsonb | NOT NULL DEFAULT '{}'::jsonb
|
|
117
|
+
# soft_deleted_at | timestamp with time zone |
|
|
118
|
+
# Indexes:
|
|
119
|
+
# message_deliveries_pkey | PRIMARY KEY btree (id)
|
|
120
|
+
# message_deliveries_transport_message_id_key | UNIQUE btree (transport_message_id)
|
|
121
|
+
# message_deliveries_recipient_id_index | btree (recipient_id)
|
|
122
|
+
# message_deliveries_sent_at_index | btree (sent_at)
|
|
123
|
+
# Foreign key constraints:
|
|
124
|
+
# message_deliveries_recipient_id_fkey | (recipient_id) REFERENCES customers(id) ON DELETE SET NULL
|
|
125
|
+
# Referenced By:
|
|
126
|
+
# message_bodies | message_bodies_delivery_id_fkey | (delivery_id) REFERENCES message_deliveries(id) ON DELETE CASCADE
|
|
127
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "mail"
|
|
5
|
+
require "premailer"
|
|
6
|
+
|
|
7
|
+
require "webhookdb/message/transport"
|
|
8
|
+
|
|
9
|
+
class Webhookdb::Message::EmailTransport < Webhookdb::Message::Transport
|
|
10
|
+
include Appydays::Configurable
|
|
11
|
+
|
|
12
|
+
register_transport(:email)
|
|
13
|
+
|
|
14
|
+
configurable(:email) do
|
|
15
|
+
setting :allowlist, ["*@lithic.tech", "*@webhookdb.com"], convert: ->(s) { s.split }
|
|
16
|
+
setting :from, "WebhookDB <hello@webhookdb.com>"
|
|
17
|
+
|
|
18
|
+
setting :smtp_host, "localhost"
|
|
19
|
+
setting :smtp_port, 18_012
|
|
20
|
+
setting :smtp_user, ""
|
|
21
|
+
setting :smtp_password, ""
|
|
22
|
+
setting :smtp_starttls, false
|
|
23
|
+
|
|
24
|
+
# If this is a recognized value, certain special behavior is used,
|
|
25
|
+
# particularly around message ids and metadata.
|
|
26
|
+
# Currently supported values:
|
|
27
|
+
#
|
|
28
|
+
# - postmark: Set the X-PM-Metadata-messageid field and some other fields.
|
|
29
|
+
#
|
|
30
|
+
# If you need other behavior, you can open an issue or pull request.
|
|
31
|
+
# Unsupported values are ignored.
|
|
32
|
+
setting :smtp_provider, ""
|
|
33
|
+
# Additional headers sent in each email.
|
|
34
|
+
setting :smtp_headers, {}, convert: ->(s) { JSON.parse(s) }
|
|
35
|
+
|
|
36
|
+
# Only really used during testing and development.
|
|
37
|
+
# We don't expect this to be running in production.
|
|
38
|
+
setting :mailpit_url, "http://localhost:18011"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def type
|
|
42
|
+
return :email
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def service
|
|
46
|
+
return "smtp"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def supports_layout?
|
|
50
|
+
return true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_bodies(delivery, content)
|
|
54
|
+
pm = Premailer.new(
|
|
55
|
+
content.to_s,
|
|
56
|
+
with_html_string: true,
|
|
57
|
+
warn_level: Premailer::Warnings::SAFE,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
subject = content[:subject]
|
|
62
|
+
rescue TypeError, NoMethodError
|
|
63
|
+
subject = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
raise "content %p is missing a subject" % content unless subject
|
|
67
|
+
|
|
68
|
+
bodies = []
|
|
69
|
+
bodies << delivery.add_body(content: content[:subject], mediatype: "subject")
|
|
70
|
+
bodies << delivery.add_body(content: pm.to_plain_text, mediatype: "text/plain")
|
|
71
|
+
bodies << delivery.add_body(content: pm.to_inline_css, mediatype: "text/html")
|
|
72
|
+
return bodies
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def allowlisted?(address)
|
|
76
|
+
return self.class.allowlist.any? { |pattern| File.fnmatch(pattern, address) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def send!(delivery)
|
|
80
|
+
unless allowlisted?(delivery.to)
|
|
81
|
+
raise Webhookdb::Message::Transport::UndeliverableRecipient,
|
|
82
|
+
"#{delivery.to} is not allowlisted"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
from = delivery.extra_fields["from"].present? ? delivery.extra_fields["from"] : self.class.from
|
|
86
|
+
to = self.format_email_name(delivery.to, delivery.recipient&.name)
|
|
87
|
+
message_id = SecureRandom.uuid.to_s
|
|
88
|
+
this = self
|
|
89
|
+
Mail.deliver do
|
|
90
|
+
delivery_method(
|
|
91
|
+
:smtp,
|
|
92
|
+
address: this.class.smtp_host,
|
|
93
|
+
port: this.class.smtp_port,
|
|
94
|
+
user_name: this.class.smtp_user,
|
|
95
|
+
password: this.class.smtp_password,
|
|
96
|
+
enable_starttls_auto: this.class.smtp_starttls,
|
|
97
|
+
)
|
|
98
|
+
this.class.smtp_headers.each do |k, v|
|
|
99
|
+
header[k] = v
|
|
100
|
+
end
|
|
101
|
+
custom_method = :"_handle_#{this.class.smtp_provider}"
|
|
102
|
+
this.send(custom_method, delivery, self, message_id) if this.respond_to?(custom_method)
|
|
103
|
+
from from
|
|
104
|
+
to to
|
|
105
|
+
reply_to(delivery.extra_fields["reply_to"]) if delivery.extra_fields["reply_to"].present?
|
|
106
|
+
subject delivery.body_with_mediatype("subject")&.content
|
|
107
|
+
text_part do
|
|
108
|
+
content_type "text/plain; charset=UTF-8"
|
|
109
|
+
body delivery.body_with_mediatype!("text/plain")&.content
|
|
110
|
+
end
|
|
111
|
+
html_part do
|
|
112
|
+
content_type "text/html; charset=UTF-8"
|
|
113
|
+
body delivery.body_with_mediatype!("text/html")&.content
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
return message_id
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
protected def extract_email_part(email)
|
|
120
|
+
split = /(?:(?<address>.+)\s)?<?(?<email>.+@[^>]+)>?/.match(email)
|
|
121
|
+
return split[:email]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def format_email_name(email, name)
|
|
125
|
+
return email if name.blank?
|
|
126
|
+
return "#{name} <#{email}>"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def _handle_postmark(delivery, mail, message_id)
|
|
130
|
+
mail.header["X-PM-Metadata-messageid"] = message_id
|
|
131
|
+
mail.header["X-PM-Tag"] = delivery.template
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/message/transport"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Message::FakeTransport < Webhookdb::Message::Transport
|
|
6
|
+
extend Webhookdb::MethodUtilities
|
|
7
|
+
|
|
8
|
+
register_transport(:fake)
|
|
9
|
+
|
|
10
|
+
singleton_attr_reader :sent_deliveries
|
|
11
|
+
@sent_deliveries = []
|
|
12
|
+
|
|
13
|
+
singleton_attr_accessor :disable_func
|
|
14
|
+
@disable_func = nil
|
|
15
|
+
|
|
16
|
+
singleton_attr_accessor :return_nil_on_send
|
|
17
|
+
@return_nil_on_send = false
|
|
18
|
+
|
|
19
|
+
def self.reset!
|
|
20
|
+
self.sent_deliveries.clear
|
|
21
|
+
self.disable_func = nil
|
|
22
|
+
self.return_nil_on_send = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def type
|
|
26
|
+
return :fake
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def service
|
|
30
|
+
return "fake"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def supports_layout?
|
|
34
|
+
return true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def add_bodies(delivery, content)
|
|
38
|
+
bodies = []
|
|
39
|
+
bodies << delivery.add_body(content: content.to_s, mediatype: "text/plain")
|
|
40
|
+
return bodies
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def send!(delivery)
|
|
44
|
+
if Webhookdb::Message::FakeTransport.disable_func&.call(delivery)
|
|
45
|
+
raise Webhookdb::Message::Transport::UndeliverableRecipient,
|
|
46
|
+
"Did not deliver Delivery[#{delivery.id}] to #{delivery.to} because it was skipped by disable_func"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Webhookdb.logger.debug "Storing Delivery[%d] to %s as sent" % [delivery.id, delivery.to]
|
|
50
|
+
Webhookdb::Message::FakeTransport.sent_deliveries << delivery
|
|
51
|
+
return nil if Webhookdb::Message::FakeTransport.return_nil_on_send
|
|
52
|
+
return "#{delivery.id}-#{SecureRandom.hex(6)}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Webhookdb::Message
|
|
4
|
+
class CustomerDrop < Liquid::Drop
|
|
5
|
+
def initialize(recipient)
|
|
6
|
+
@recipient = recipient
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to
|
|
11
|
+
return @recipient.to
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
return @recipient.customer&.name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def greeting
|
|
19
|
+
return nil unless @recipient.customer
|
|
20
|
+
return @recipient.customer.greeting
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class EnvironmentDrop < Liquid::Drop
|
|
25
|
+
def name
|
|
26
|
+
return Webhookdb::RACK_ENV
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/message"
|
|
4
|
+
|
|
5
|
+
# Template is the base class for all message templates.
|
|
6
|
+
# Every message (email, and/or SMS, etc) has a Template
|
|
7
|
+
# that defines how to look up its text template and the dynamic data
|
|
8
|
+
# for rendering into the template.
|
|
9
|
+
# Subclasses should override methods to control the rendering behavior;
|
|
10
|
+
# see the method docs to see what is expected to be overridden.
|
|
11
|
+
class Webhookdb::Message::Template
|
|
12
|
+
# Override to return a new version of the template,
|
|
13
|
+
# with its data fixtured. It is used for message previewing.
|
|
14
|
+
# The argument is the message recipient,
|
|
15
|
+
# because they may need to be used to build resources off of.
|
|
16
|
+
#
|
|
17
|
+
# Only needs to be overridden if the template constructor takes arguments.
|
|
18
|
+
# Assume that fixtures are loaded when this method is called;
|
|
19
|
+
# do not require them as part of your code.
|
|
20
|
+
def self.fixtured(_recipient)
|
|
21
|
+
return self.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def dispatch(to, transport: Webhookdb::Message::DEFAULT_TRANSPORT)
|
|
25
|
+
return Webhookdb::Message.dispatch(self, to, transport)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dispatch_email(to)
|
|
29
|
+
return self.dispatch(to, transport: :email)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The folder containing this template. Templates in the root template directory should use nil.
|
|
33
|
+
def template_folder
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The name of the template. By default, it is tied to the name of the class,
|
|
38
|
+
# so Messages::NewCustomer looks for 'new_customer' templates.
|
|
39
|
+
# However, a subclass can override this to not tie the template to the class name.
|
|
40
|
+
def template_name
|
|
41
|
+
return self.class.name.demodulize.underscore
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def full_template_name
|
|
45
|
+
fld = self.template_folder.present? ? "#{self.template_folder}/" : ""
|
|
46
|
+
return "#{fld}#{self.template_name}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def template_path(transport)
|
|
50
|
+
return Webhookdb::Message::DATA_DIR + "templates/#{self.full_template_name}.#{transport}.liquid"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The layout for the template. See the 'layouts' folder in the message data directory.
|
|
54
|
+
def layout
|
|
55
|
+
return "standard"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def layout_path(transport)
|
|
59
|
+
return nil if self.layout.nil?
|
|
60
|
+
return Webhookdb::Message::DATA_DIR + "layouts/#{self.layout}.#{transport}.liquid"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The hash that is used for the rendering of the template.
|
|
64
|
+
# These must be simple types (strings, ints, bools, etc), or Liquid::Drop subclasses.
|
|
65
|
+
# By default, templates are rendered with some default variables,
|
|
66
|
+
# such as 'recipient', 'environment', and 'app_url'.
|
|
67
|
+
def liquid_drops
|
|
68
|
+
return {
|
|
69
|
+
api_url: Webhookdb.api_url,
|
|
70
|
+
support_email: Webhookdb.support_email,
|
|
71
|
+
oss_repo: Webhookdb.oss_repo_url,
|
|
72
|
+
docs_url: "https://docs.webhookdb.com",
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Liquify wraps any object in a Liquification.
|
|
77
|
+
def liquify(o)
|
|
78
|
+
return Webhookdb::Liquid::Liquification.new(o)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Return a hash of special fields for that this delivery should use.
|
|
82
|
+
# Necessary for deliveries that need to carry along some metadata. For example:
|
|
83
|
+
# {'reply_to' => 'customer@gmail.com'}
|
|
84
|
+
# for customers who send support requests.
|
|
85
|
+
# These fields are subject to flux and change and may only be applicable to some transports.
|
|
86
|
+
def extra_fields
|
|
87
|
+
return {}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/message"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Message::Transport
|
|
6
|
+
extend Webhookdb::MethodUtilities
|
|
7
|
+
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
class UndeliverableRecipient < Error; end
|
|
10
|
+
|
|
11
|
+
singleton_attr_reader :transports
|
|
12
|
+
@transports = {}
|
|
13
|
+
|
|
14
|
+
singleton_attr_accessor :override
|
|
15
|
+
|
|
16
|
+
def self.register_transport(type)
|
|
17
|
+
Webhookdb::Message::Transport.transports[type] = self.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.for(type)
|
|
21
|
+
type = Webhookdb::Message::Transport.override || type
|
|
22
|
+
return Webhookdb::Message::Transport.transports[type.to_sym]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.for!(type)
|
|
26
|
+
(t = self.for(type)) or raise Webhookdb::Message::InvalidTransportError, "invalid transport: %p" % type
|
|
27
|
+
return t
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Override this if a transport needs a different 'to' than the email,
|
|
31
|
+
# like for text messages.
|
|
32
|
+
def recipient(to)
|
|
33
|
+
if to.is_a?(Webhookdb::Customer)
|
|
34
|
+
(email = to.email) or raise "Customer #{to.id} has no default email"
|
|
35
|
+
return Webhookdb::Message::Recipient.new(email, to)
|
|
36
|
+
end
|
|
37
|
+
return Webhookdb::Message::Recipient.new(to, nil)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def send!(_delivery)
|
|
41
|
+
raise "Must implement in subclass. Call 3rd party with message/content and return transport ID."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "liquid"
|
|
5
|
+
|
|
6
|
+
module Webhookdb::Messages
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Webhookdb::Liquid
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Webhookdb::Message
|
|
13
|
+
include Appydays::Configurable
|
|
14
|
+
extend Webhookdb::MethodUtilities
|
|
15
|
+
|
|
16
|
+
require "webhookdb/liquid/expose"
|
|
17
|
+
require "webhookdb/liquid/filters"
|
|
18
|
+
require "webhookdb/liquid/liquification"
|
|
19
|
+
require "webhookdb/liquid/partial"
|
|
20
|
+
|
|
21
|
+
require "webhookdb/message/email_transport"
|
|
22
|
+
require "webhookdb/message/fake_transport"
|
|
23
|
+
require "webhookdb/message/transport"
|
|
24
|
+
require "webhookdb/message/liquid_drops"
|
|
25
|
+
require "webhookdb/message/template"
|
|
26
|
+
|
|
27
|
+
DEFAULT_TRANSPORT = :email
|
|
28
|
+
DATA_DIR = Webhookdb::DATA_DIR + "messages"
|
|
29
|
+
|
|
30
|
+
configurable(:messages) do
|
|
31
|
+
after_configured do
|
|
32
|
+
Liquid::Template.error_mode = :strict
|
|
33
|
+
Liquid::Template.file_system = Liquid::LocalFileSystem.new(DATA_DIR, "%s.liquid")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create a Webhookdb::Message::Delivery ready to deliver (rendered, all bodies set up)
|
|
38
|
+
# using the given transport_type to the given user.
|
|
39
|
+
def self.dispatch(template, to, transport_type)
|
|
40
|
+
(transport = Webhookdb::Message::Transport.for(transport_type)) or
|
|
41
|
+
raise InvalidTransportError, "Invalid transport #{transport_type}"
|
|
42
|
+
recipient = transport.recipient(to)
|
|
43
|
+
|
|
44
|
+
contents = self.render(template, transport_type, recipient)
|
|
45
|
+
|
|
46
|
+
Webhookdb::Message::Delivery.db.transaction do
|
|
47
|
+
delivery = Webhookdb::Message::Delivery.create(
|
|
48
|
+
template: template.full_template_name,
|
|
49
|
+
transport_type: transport.type,
|
|
50
|
+
transport_service: transport.service,
|
|
51
|
+
to: recipient.to,
|
|
52
|
+
recipient: recipient.customer,
|
|
53
|
+
extra_fields: template.extra_fields,
|
|
54
|
+
)
|
|
55
|
+
transport.add_bodies(delivery, contents)
|
|
56
|
+
delivery.publish_deferred("dispatched", delivery.id)
|
|
57
|
+
return delivery
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Render the transport-specific version of the given template
|
|
62
|
+
# and return a the rendering (content and exposed variables).
|
|
63
|
+
#
|
|
64
|
+
# Templates can expose data to the caller by using the 'expose' tag,
|
|
65
|
+
# like {% expose subject %}Hello from Webhookdb!{% endexpose %}.
|
|
66
|
+
# This is available as [:subject] on the returned rendering.
|
|
67
|
+
def self.render(template, transport_type, recipient)
|
|
68
|
+
template_file = template.template_path(transport_type)
|
|
69
|
+
raise MissingTemplateError, "#{template_file} does not exist" unless template_file.exist?
|
|
70
|
+
|
|
71
|
+
drops = template.liquid_drops.stringify_keys.merge(
|
|
72
|
+
"recipient" => Webhookdb::Message::CustomerDrop.new(recipient),
|
|
73
|
+
"environment" => Webhookdb::Message::EnvironmentDrop.new,
|
|
74
|
+
"app_url" => Webhookdb.app_url,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
content_tmpl = Liquid::Template.parse(template_file.read)
|
|
78
|
+
# The 'expose' drop smashes data into the register.
|
|
79
|
+
# We need to keep track of the register to get the subject back out,
|
|
80
|
+
# so we need to make our own context.
|
|
81
|
+
lctx = Liquid::Context.new(
|
|
82
|
+
[drops, content_tmpl.assigns],
|
|
83
|
+
content_tmpl.instance_assigns,
|
|
84
|
+
content_tmpl.registers,
|
|
85
|
+
true,
|
|
86
|
+
content_tmpl.resource_limits,
|
|
87
|
+
)
|
|
88
|
+
content = content_tmpl.render!(lctx, strict_variables: true)
|
|
89
|
+
|
|
90
|
+
transport = Webhookdb::Message::Transport.for(transport_type)
|
|
91
|
+
if transport.supports_layout?
|
|
92
|
+
layout_file = template.layout_path(transport_type)
|
|
93
|
+
if layout_file
|
|
94
|
+
raise MissingTemplateError, "#{template_file} does not exist" unless layout_file.exist?
|
|
95
|
+
layout_tmpl = Liquid::Template.parse(layout_file.read)
|
|
96
|
+
drops["content"] = content.dup
|
|
97
|
+
content = layout_tmpl.render!(drops, strict_variables: true, registers: content_tmpl.registers)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return Rendering.new(content, lctx.registers)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.send_unsent
|
|
105
|
+
Webhookdb::Message::Delivery.unsent.each(&:send!)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class InvalidTransportError < StandardError; end
|
|
109
|
+
|
|
110
|
+
class MissingTemplateError < StandardError; end
|
|
111
|
+
|
|
112
|
+
# Presents a homogeneous interface for a given 'to' value (email vs. customer, for example).
|
|
113
|
+
# .to will always be a plain object, and .customer will be a +Webhookdb::Customer+ if present.
|
|
114
|
+
class Recipient
|
|
115
|
+
attr_reader :to, :customer
|
|
116
|
+
|
|
117
|
+
def initialize(to, customer)
|
|
118
|
+
@to = to
|
|
119
|
+
@customer = customer
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# String-like type representing the output of a rendering operation.
|
|
124
|
+
# Use [key] to access exposed variables, as per +LiquidExpose+.
|
|
125
|
+
class Rendering
|
|
126
|
+
attr_reader :contents, :exposed
|
|
127
|
+
|
|
128
|
+
def initialize(contents, exposed={})
|
|
129
|
+
@contents = contents
|
|
130
|
+
@exposed = exposed
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def [](key)
|
|
134
|
+
return self.exposed[key]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def to_s
|
|
138
|
+
return self.contents
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def respond_to_missing?(name, *args)
|
|
142
|
+
return true if super
|
|
143
|
+
return self.contents.respond_to?(name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def method_missing(name, *, &)
|
|
147
|
+
return self.contents.respond_to?(name) ? self.contents.send(name, *, &) : super
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/message/template"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Messages::ErrorIcalendarFetch < Webhookdb::Message::Template
|
|
6
|
+
def self.fixtured(_recipient)
|
|
7
|
+
sint = Webhookdb::Fixtures.service_integration.create
|
|
8
|
+
return self.new(sint, "calendar123",
|
|
9
|
+
response_status: 403, request_url: "/foo", request_method: "GET", response_body: "hi",)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(service_integration, external_calendar_id, request_url:, request_method:, response_status:,
|
|
13
|
+
response_body:)
|
|
14
|
+
@service_integration = service_integration
|
|
15
|
+
@external_calendar_id = external_calendar_id
|
|
16
|
+
@request_url = request_url
|
|
17
|
+
@request_method = request_method
|
|
18
|
+
@response_status = response_status
|
|
19
|
+
@response_body = response_body
|
|
20
|
+
super()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def signature
|
|
24
|
+
ehash = Digest::MD5.hexdigest(@kw.to_s)
|
|
25
|
+
return "msg-#{self.full_template_name}-sint:#{@service_integration.id}-eid:#{@external_calendar_id}-err:#{ehash}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def template_folder = "errors"
|
|
29
|
+
def template_name = "icalendar_fetch"
|
|
30
|
+
|
|
31
|
+
def liquid_drops
|
|
32
|
+
return super.merge(
|
|
33
|
+
service_name: @service_integration.service_name,
|
|
34
|
+
opaque_id: @service_integration.opaque_id,
|
|
35
|
+
request_method: @request_method,
|
|
36
|
+
request_url: @request_url,
|
|
37
|
+
response_status: @response_status,
|
|
38
|
+
response_body: @response_body,
|
|
39
|
+
external_calendar_id: @external_calendar_id,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/message/template"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::Messages::Invite < Webhookdb::Message::Template
|
|
6
|
+
def self.fixtured(recipient)
|
|
7
|
+
org = Webhookdb::Fixtures.organization.with_member(recipient).create
|
|
8
|
+
membership = org.all_memberships[0]
|
|
9
|
+
return self.new(membership)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(membership)
|
|
13
|
+
@membership = membership
|
|
14
|
+
super()
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def liquid_drops
|
|
18
|
+
return super.merge(
|
|
19
|
+
organization_name: @membership.organization_name,
|
|
20
|
+
invite_code: @membership.invitation_code,
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|