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,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/api"
|
|
4
|
+
require "webhookdb/oauth"
|
|
5
|
+
require "webhookdb/service/view_api"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::API::Install < Webhookdb::API::V1
|
|
8
|
+
include Webhookdb::Service::ViewApi
|
|
9
|
+
|
|
10
|
+
namespace :install do
|
|
11
|
+
helpers do
|
|
12
|
+
def lookup_session!
|
|
13
|
+
session = Webhookdb::Oauth::Session.usable.where(oauth_state: params[:state]).first
|
|
14
|
+
forbidden! unless session
|
|
15
|
+
return session
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def handle_login(email:, session:, action_url:)
|
|
19
|
+
new_customer, me = Webhookdb::Customer.find_or_create_for_email(email)
|
|
20
|
+
me.reset_codes_dataset.usable.each(&:expire!)
|
|
21
|
+
me.add_reset_code(transport: "email")
|
|
22
|
+
session.update(customer: me)
|
|
23
|
+
rendered = render_liquid(
|
|
24
|
+
"messages/web/install-customer-login.liquid",
|
|
25
|
+
serialize_view_params: true,
|
|
26
|
+
vars: {
|
|
27
|
+
view: "otp",
|
|
28
|
+
action_url:,
|
|
29
|
+
oauth_state: session.oauth_state,
|
|
30
|
+
new_customer:,
|
|
31
|
+
email:,
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
status 200
|
|
35
|
+
body rendered
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def find_and_verify_user(email:, otp_token:)
|
|
39
|
+
(me = Webhookdb::Customer.with_email(email)) or forbidden!
|
|
40
|
+
begin
|
|
41
|
+
Webhookdb::Customer::ResetCode.use_code_with_token(otp_token) do |code|
|
|
42
|
+
raise Webhookdb::Customer::ResetCode::Unusable unless code.customer === me
|
|
43
|
+
end
|
|
44
|
+
rescue Webhookdb::Customer::ResetCode::Unusable
|
|
45
|
+
raise FormError.new("Sorry, that token is invalid. Please try again.", 403)
|
|
46
|
+
end
|
|
47
|
+
return me
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
route_param :oauth_provider, type: String, values: Webhookdb::Oauth.registry.keys do
|
|
52
|
+
helpers do
|
|
53
|
+
def oauth_provider
|
|
54
|
+
return @oauth_provider ||= Webhookdb::Oauth.provider(params[:oauth_provider])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def finish_org_setup(organization:, tokens:, scope:)
|
|
58
|
+
organization.prepare_database_connections?
|
|
59
|
+
oauth_provider.build_marketplace_integrations(organization:, tokens:, scope:)
|
|
60
|
+
rendered = render_liquid(
|
|
61
|
+
"messages/web/install-success.liquid",
|
|
62
|
+
serialize_view_params: true,
|
|
63
|
+
vars: {
|
|
64
|
+
app_name: oauth_provider.app_name,
|
|
65
|
+
database_url: organization.readonly_connection_url,
|
|
66
|
+
supports_webhooks: oauth_provider.supports_webhooks?,
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
status 200
|
|
70
|
+
body rendered
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def exchange_authorization_code(code)
|
|
74
|
+
return oauth_provider.exchange_authorization_code(code:)
|
|
75
|
+
rescue Webhookdb::Http::Error => e
|
|
76
|
+
logger.warn "oauth_exchange_error", exception: e
|
|
77
|
+
raise FormError.new(
|
|
78
|
+
"Something went wrong getting your access token from #{oauth_provider.app_name}. Please start over",
|
|
79
|
+
400,
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_admin_membership(customer)
|
|
84
|
+
_created, membership = Webhookdb::Customer.find_or_create_default_organization(customer)
|
|
85
|
+
membership = customer.verified_memberships.find(&:admin?) unless membership.admin?
|
|
86
|
+
return membership if membership
|
|
87
|
+
raise FormError.new(
|
|
88
|
+
"You must be an administrator of your WebhookDB organization to set up this app.",
|
|
89
|
+
403,
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
get do
|
|
95
|
+
rendered = render_liquid(
|
|
96
|
+
"messages/web/install.liquid",
|
|
97
|
+
serialize_view_params: true,
|
|
98
|
+
vars: {app_name: oauth_provider.app_name, action_url: "/v1/install/#{oauth_provider.key}"},
|
|
99
|
+
)
|
|
100
|
+
status 200
|
|
101
|
+
body rendered
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
post do
|
|
105
|
+
oauth_state = SecureRandom.hex(16)
|
|
106
|
+
Webhookdb::Oauth::Session.create(
|
|
107
|
+
oauth_state:,
|
|
108
|
+
**Webhookdb::Oauth::Session.params_for_request(request),
|
|
109
|
+
)
|
|
110
|
+
auth_url = oauth_provider.authorization_url(state: oauth_state)
|
|
111
|
+
redirect auth_url
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
params do
|
|
115
|
+
requires :code, type: String, desc: "Authorization code that we exchange for tokens."
|
|
116
|
+
requires :state, type: String, desc: "The user session info string that we provided to the oauth flow."
|
|
117
|
+
end
|
|
118
|
+
get :callback do
|
|
119
|
+
session = lookup_session!
|
|
120
|
+
code = params[:code]
|
|
121
|
+
if oauth_provider.requires_webhookdb_auth?
|
|
122
|
+
session.update(authorization_code: code)
|
|
123
|
+
redirect "/v1/install/#{oauth_provider.key}/login?state=#{session.oauth_state}"
|
|
124
|
+
else
|
|
125
|
+
scope = {}
|
|
126
|
+
# Order of operations here is:
|
|
127
|
+
# - Exchange token. We need the token to create the customer so it comes first.
|
|
128
|
+
# - Create the customer, find the membership, and create replicators.
|
|
129
|
+
# - If this last step fails, the code becomes invalid, which is annoying,
|
|
130
|
+
# but should be rare and not a big deal to start over.
|
|
131
|
+
tokens = exchange_authorization_code(code)
|
|
132
|
+
session.db.transaction do
|
|
133
|
+
_created, customer = oauth_provider.find_or_create_customer(tokens:, scope:)
|
|
134
|
+
membership = find_admin_membership(customer)
|
|
135
|
+
finish_org_setup(organization: membership.organization, tokens:, scope:)
|
|
136
|
+
session.update(customer:, used_at: Time.now, authorization_code: code)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
params do
|
|
142
|
+
requires :state, type: String
|
|
143
|
+
end
|
|
144
|
+
get :login do
|
|
145
|
+
session = lookup_session!
|
|
146
|
+
rendered = render_liquid(
|
|
147
|
+
"messages/web/install-customer-login.liquid",
|
|
148
|
+
serialize_view_params: true,
|
|
149
|
+
vars: {
|
|
150
|
+
view: "email",
|
|
151
|
+
action_url: "/v1/install/#{oauth_provider.key}/login",
|
|
152
|
+
oauth_state: session.oauth_state,
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
status 200
|
|
156
|
+
body rendered
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
params do
|
|
160
|
+
requires :state, type: String, desc: "the user session info string that we provided to Front"
|
|
161
|
+
optional :email, type: String
|
|
162
|
+
optional :otp_token, type: String
|
|
163
|
+
end
|
|
164
|
+
post :login do
|
|
165
|
+
session = lookup_session!
|
|
166
|
+
email = params[:email]
|
|
167
|
+
raise FormError.new("Email is required", 400) unless email.present?
|
|
168
|
+
otp_token = params[:otp_token]
|
|
169
|
+
if otp_token
|
|
170
|
+
# Order of operations here is:
|
|
171
|
+
# - Verify the OTP
|
|
172
|
+
# - Make sure we can find a valid admin membership
|
|
173
|
+
# - Only then do we exchange the token.
|
|
174
|
+
# - Setup replicators.
|
|
175
|
+
customer = find_and_verify_user(email:, otp_token:)
|
|
176
|
+
membership = find_admin_membership(customer)
|
|
177
|
+
tokens = exchange_authorization_code(session.authorization_code)
|
|
178
|
+
session.db.transaction do
|
|
179
|
+
finish_org_setup(organization: membership.organization, tokens:, scope: {})
|
|
180
|
+
session.update(used_at: Time.now)
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
handle_login(email:, session:, action_url: "/v1/install/#{oauth_provider.key}/login")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
resource :front do
|
|
189
|
+
post :webhook do
|
|
190
|
+
is_initial_request = request.headers["X-Front-Challenge"].present?
|
|
191
|
+
if is_initial_request
|
|
192
|
+
whresp = Webhookdb::Front.initial_verification_request_response(request)
|
|
193
|
+
s_status, s_headers, s_body = whresp.to_rack
|
|
194
|
+
s_headers.each { |k, v| header k, v }
|
|
195
|
+
if s_headers["Content-Type"] == "application/json"
|
|
196
|
+
body Oj.load(s_body)
|
|
197
|
+
else
|
|
198
|
+
env["api.format"] = :binary
|
|
199
|
+
body s_body
|
|
200
|
+
end
|
|
201
|
+
status s_status
|
|
202
|
+
break
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
resource_url = params.dig(:payload, :_links, :self)
|
|
206
|
+
handle_webhook_request("front_marketplace_host-#{resource_url || '?'}") do
|
|
207
|
+
if resource_url.nil?
|
|
208
|
+
logger.warn "front_webhook_empty_resource_url"
|
|
209
|
+
status 200
|
|
210
|
+
present({message: "unregistered/empty app"})
|
|
211
|
+
next :pass
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# In cases where there is a change to a message, the event payload will have a "target" object and that object
|
|
215
|
+
# will have a "type" of "message". In cases where there is a change to a conversation, there will be no
|
|
216
|
+
# "target" object. In these cases the conversation resource is in the event as the "conversation" object.
|
|
217
|
+
target_type = params.dig(:payload, :target, :_meta, :type) || "conversation"
|
|
218
|
+
service_name = "front_#{target_type}_v1"
|
|
219
|
+
api_url = URI.parse(resource_url).host
|
|
220
|
+
unless (root_sint = Webhookdb::ServiceIntegration[service_name: "front_marketplace_root_v1", api_url:])
|
|
221
|
+
logger.warn "front_webhook_unregistered_app", front_api_url: api_url
|
|
222
|
+
status 200
|
|
223
|
+
present({message: "unregistered app"})
|
|
224
|
+
next :pass
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
handling_sint = root_sint.recursive_dependents.find { |d| d.service_name == service_name }
|
|
228
|
+
if handling_sint.nil?
|
|
229
|
+
logger.warn "front_webhook_invalid_topic", front_api_url: api_url, front_topic: target_type
|
|
230
|
+
status 200
|
|
231
|
+
present({message: "invalid topic"})
|
|
232
|
+
next :pass
|
|
233
|
+
end
|
|
234
|
+
next handling_sint
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
resource :intercom do
|
|
240
|
+
post :webhook do
|
|
241
|
+
# Because the `_webhook_response` function is always the same here, I'm wondering if it's even
|
|
242
|
+
# advisable to do the integration lookup before performing a webhook verification when we don't
|
|
243
|
+
# need that info. Something to consider upon refactor
|
|
244
|
+
|
|
245
|
+
handle_webhook_request("intercom_marketplace_appid-#{params[:app_id] || '?'}") do
|
|
246
|
+
app_id = params[:app_id]
|
|
247
|
+
root_sint = Webhookdb::ServiceIntegration[service_name: "intercom_marketplace_root_v1", api_url: app_id]
|
|
248
|
+
if root_sint.nil?
|
|
249
|
+
logger.warn "intercom_webhook_unregistered_app", intercom_app_id: app_id
|
|
250
|
+
status 200
|
|
251
|
+
present({message: "unregistered app"})
|
|
252
|
+
next :pass
|
|
253
|
+
end
|
|
254
|
+
# Notification topics are formatted like "{model}.{thing that happened}" (e.g. "contact.created")
|
|
255
|
+
# to get the model type of the notification, for our purposes we can just grab that first chunk
|
|
256
|
+
type = params[:topic].split(".")[0]
|
|
257
|
+
handling_type = "intercom_#{type}_v1"
|
|
258
|
+
unless (handling_sint = root_sint.recursive_dependents.find { |d| d.service_name == handling_type })
|
|
259
|
+
logger.warn "intercom_webhook_invalid_topic", intercom_app_id: app_id, intercom_topic: params[:topic]
|
|
260
|
+
status 200
|
|
261
|
+
present({message: "invalid topic"})
|
|
262
|
+
next :pass
|
|
263
|
+
end
|
|
264
|
+
next handling_sint
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
params do
|
|
269
|
+
requires :app_id
|
|
270
|
+
end
|
|
271
|
+
post :uninstall do
|
|
272
|
+
# TODO: Verify the headers are valid
|
|
273
|
+
# We want to delete all the integrations associated with the app_id.
|
|
274
|
+
root_sint = Webhookdb::ServiceIntegration[service_name: "intercom_marketplace_root_v1",
|
|
275
|
+
api_url: params["app_id"]]
|
|
276
|
+
root_sint.destroy_self_and_all_dependents
|
|
277
|
+
status 200
|
|
278
|
+
present({o: "k"})
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
params do
|
|
282
|
+
requires :workspace_id
|
|
283
|
+
end
|
|
284
|
+
post :health do
|
|
285
|
+
# TODO: Verify the headers are valid
|
|
286
|
+
# For now we are just returning "OK" per the specification:
|
|
287
|
+
# https://developers.intercom.com/docs/build-an-integration/learn-more/installation-health-check
|
|
288
|
+
# An interesting point is that this endpoint recieves a value called "workspace_id" but it is
|
|
289
|
+
# identical to the "app_id" value we get from the `/me` endpoint. It just has a different name here, for
|
|
290
|
+
# some reason.
|
|
291
|
+
status 200
|
|
292
|
+
present({state: "OK"})
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "grape"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/api"
|
|
6
|
+
|
|
7
|
+
class Webhookdb::API::Me < Webhookdb::API::V1
|
|
8
|
+
resource :me do
|
|
9
|
+
desc "Return the current customer"
|
|
10
|
+
get do
|
|
11
|
+
customer = current_customer
|
|
12
|
+
present customer, with: Webhookdb::API::CurrentCustomerEntity, env:
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
resource :organization_memberships do
|
|
16
|
+
desc "Return all organizations the customer is part of."
|
|
17
|
+
params do
|
|
18
|
+
optional :active_org_identifier
|
|
19
|
+
end
|
|
20
|
+
get do
|
|
21
|
+
customer = current_customer
|
|
22
|
+
active_org_ids = Webhookdb::Organization.with_identifier(params[:active_org_identifier]).select_map(:id)
|
|
23
|
+
memberships, invited = Webhookdb::OrganizationMembership.where(customer:).all.partition(&:verified)
|
|
24
|
+
blocks = Webhookdb::Formatting.blocks
|
|
25
|
+
unless memberships.empty?
|
|
26
|
+
blocks.line("You are a member of the following organizations:")
|
|
27
|
+
blocks.blank
|
|
28
|
+
# the word "Status" here is referring to whether the org is "active" in the CLI. This designation
|
|
29
|
+
# is added by the client
|
|
30
|
+
rows = memberships.map do |m|
|
|
31
|
+
cli_status = active_org_ids.include?(m.organization_id) ? "active" : ""
|
|
32
|
+
[m.organization.name, m.organization.key, m.status, cli_status]
|
|
33
|
+
end
|
|
34
|
+
blocks.table(["Name", "Key", "Role", "Status"], rows)
|
|
35
|
+
end
|
|
36
|
+
blocks.blank if memberships.present? && invited.present?
|
|
37
|
+
unless invited.empty?
|
|
38
|
+
blocks.line("You have been invited to the following organizations:")
|
|
39
|
+
blocks.blank
|
|
40
|
+
rows = invited.map do |m|
|
|
41
|
+
[m.organization.name, m.organization.key, m.invitation_code]
|
|
42
|
+
end
|
|
43
|
+
blocks.table(["Name", "Key", "Join Code"], rows)
|
|
44
|
+
blocks.blank
|
|
45
|
+
blocks.line("To join an invited org, use: webhookdb org join [join code]")
|
|
46
|
+
end
|
|
47
|
+
blocks.line("You aren't affiliated with any organizations yet.") if memberships.empty? && invited.empty?
|
|
48
|
+
r = {blocks: blocks.as_json}
|
|
49
|
+
present r
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "grape"
|
|
4
|
+
|
|
5
|
+
require "webhookdb/api"
|
|
6
|
+
require "webhookdb/admin_api"
|
|
7
|
+
|
|
8
|
+
class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
9
|
+
include Webhookdb::Service::Types
|
|
10
|
+
|
|
11
|
+
resource :organizations do
|
|
12
|
+
route_param :org_identifier, type: String do
|
|
13
|
+
helpers do
|
|
14
|
+
def check_self_role_modification!(email)
|
|
15
|
+
return unless email == current_customer.email
|
|
16
|
+
return if params.key?(:guard_confirm)
|
|
17
|
+
Webhookdb::API::Helpers.prompt_for_required_param!(
|
|
18
|
+
request,
|
|
19
|
+
:guard_confirm,
|
|
20
|
+
"WARNING: You are modifying your own permissions. Enter to proceed, or Ctrl+C to quit:",
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If the current customer is the last admin of the org, we cannot allow them to remove themselves,
|
|
25
|
+
# since the org would have no admins.
|
|
26
|
+
def roll_back_if_no_admins!(org)
|
|
27
|
+
has_admins = !org.verified_memberships_dataset.
|
|
28
|
+
where(membership_role: Webhookdb::Role.admin_role).
|
|
29
|
+
empty?
|
|
30
|
+
return if has_admins
|
|
31
|
+
msg = "Sorry, you are the last admin in #{org.name} and cannot remove yourself. " \
|
|
32
|
+
"If you want to close down this org, Use 'webhookdb org close'"
|
|
33
|
+
merror!(409, msg, code: "remove_last_admin", rollback_db: org.db)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "Return organization with the given identifier."
|
|
38
|
+
get do
|
|
39
|
+
_customer = current_customer
|
|
40
|
+
org = lookup_org!
|
|
41
|
+
present org, with: Webhookdb::API::OrganizationEntity
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
resource :members do
|
|
45
|
+
desc "Return all customers associated with the organization"
|
|
46
|
+
get do
|
|
47
|
+
org_memberships = lookup_org!.all_memberships
|
|
48
|
+
present_collection org_memberships, with: Webhookdb::API::OrganizationMembershipEntity
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
resource :services do
|
|
53
|
+
desc "Returns a list of all available services."
|
|
54
|
+
get do
|
|
55
|
+
_customer = current_customer
|
|
56
|
+
org = lookup_org!
|
|
57
|
+
fake_entities = org.available_replicator_names.sort.map { |name| {name:} }
|
|
58
|
+
message = "Run `webhookdb integrations create [service name]` to start replicating data to your database."
|
|
59
|
+
present_collection fake_entities, with: Webhookdb::API::ServiceEntity, message:
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "Generates an invitation code for a user, adds pending membership in the organization."
|
|
64
|
+
params do
|
|
65
|
+
optional :email, type: String, coerce_with: NormalizedEmail,
|
|
66
|
+
prompt: "Enter the email to send the invitation to:"
|
|
67
|
+
optional :role_name,
|
|
68
|
+
type: String,
|
|
69
|
+
values: Webhookdb::OrganizationMembership::VALID_ROLE_NAMES,
|
|
70
|
+
default: "member"
|
|
71
|
+
end
|
|
72
|
+
post :invite do
|
|
73
|
+
customer = current_customer
|
|
74
|
+
org = lookup_org!
|
|
75
|
+
ensure_admin!
|
|
76
|
+
customer.db.transaction do
|
|
77
|
+
email = params[:email]
|
|
78
|
+
# cannot use find_or_create_or_find here because all customers must be created with a random password,
|
|
79
|
+
# which can't be included in find parameters
|
|
80
|
+
invitee = Webhookdb::Customer[email:] ||
|
|
81
|
+
Webhookdb::Customer.create(email:, password: SecureRandom.hex(8))
|
|
82
|
+
|
|
83
|
+
membership = org.all_memberships_dataset[customer: invitee]
|
|
84
|
+
merror!(400, "That person is already a member of the organization.") if membership&.verified?
|
|
85
|
+
|
|
86
|
+
membership ||= Webhookdb::OrganizationMembership.new(
|
|
87
|
+
verified: false,
|
|
88
|
+
organization: org,
|
|
89
|
+
customer: invitee,
|
|
90
|
+
)
|
|
91
|
+
membership.membership_role = Webhookdb::Role.find_or_create_or_find(name: params[:role_name])
|
|
92
|
+
membership.invitation_code = "join-" + SecureRandom.hex(4)
|
|
93
|
+
membership.save_changes
|
|
94
|
+
|
|
95
|
+
membership.publish_deferred("invite", membership.id)
|
|
96
|
+
message = "An invitation to organization #{org.name} has been sent to #{email}.\n" \
|
|
97
|
+
"Their invite code is:\n #{membership.invitation_code}"
|
|
98
|
+
status 200
|
|
99
|
+
present membership, with: Webhookdb::API::OrganizationMembershipEntity, message:
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
desc "Allows organization admin to remove customer from an organization"
|
|
104
|
+
params do
|
|
105
|
+
optional :email, type: String, coerce_with: NormalizedEmail,
|
|
106
|
+
prompt: "Enter the email of the member you are removing permissions from:"
|
|
107
|
+
optional :guard_confirm
|
|
108
|
+
end
|
|
109
|
+
post :remove_member do
|
|
110
|
+
customer = current_customer
|
|
111
|
+
org = lookup_org!
|
|
112
|
+
ensure_admin!
|
|
113
|
+
email = params[:email]
|
|
114
|
+
check_self_role_modification!(params[:email])
|
|
115
|
+
customer.db.transaction do
|
|
116
|
+
to_delete = org.all_memberships_dataset.where(customer: Webhookdb::Customer[email:])
|
|
117
|
+
merror!(400, "That user is not a member of #{org.name}.") if to_delete.empty?
|
|
118
|
+
to_delete.delete
|
|
119
|
+
roll_back_if_no_admins!(org)
|
|
120
|
+
status 200
|
|
121
|
+
present({}, with: Webhookdb::AdminAPI::BaseEntity,
|
|
122
|
+
message: "#{email} is no longer a part of #{org.name}.",)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
desc "Updates the field on an org."
|
|
127
|
+
params do
|
|
128
|
+
requires :field, type: String
|
|
129
|
+
requires :value, type: String
|
|
130
|
+
end
|
|
131
|
+
post :update do
|
|
132
|
+
customer = current_customer
|
|
133
|
+
org = lookup_org!
|
|
134
|
+
ensure_admin!
|
|
135
|
+
field_name = params[:field].downcase
|
|
136
|
+
unless org.cli_editable_fields.include?(field_name)
|
|
137
|
+
merror!(403, "That field is not editable from the command line")
|
|
138
|
+
end
|
|
139
|
+
customer.db.transaction do
|
|
140
|
+
org.send(:"#{field_name}=", params[:value])
|
|
141
|
+
org.save_changes
|
|
142
|
+
status 200
|
|
143
|
+
present org, with: Webhookdb::API::OrganizationEntity,
|
|
144
|
+
message: "You have successfully updated the organization #{org.name}."
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
desc "Allows organization admin to change customer's role in an organization"
|
|
149
|
+
params do
|
|
150
|
+
optional :emails, type: [String], coerce_with: CommaSepArray,
|
|
151
|
+
prompt: "Enter the emails to modify the roles of as a comma-separated list:"
|
|
152
|
+
optional :role_name, type: String, values: Webhookdb::OrganizationMembership::VALID_ROLE_NAMES,
|
|
153
|
+
prompt: "Enter the name of the role to assign " \
|
|
154
|
+
"(#{Webhookdb::OrganizationMembership::VALID_ROLE_NAMES.join(', ')}): "
|
|
155
|
+
optional :guard_confirm
|
|
156
|
+
end
|
|
157
|
+
post :change_roles do
|
|
158
|
+
customer = current_customer
|
|
159
|
+
org = lookup_org!
|
|
160
|
+
ensure_admin!
|
|
161
|
+
params[:emails].each { |e| check_self_role_modification!(e) }
|
|
162
|
+
customer.db.transaction do
|
|
163
|
+
new_role = Webhookdb::Role.find_or_create_or_find(name: params[:role_name])
|
|
164
|
+
memberships = org.all_memberships_dataset.where(customer: Webhookdb::Customer.where(email: params[:emails]))
|
|
165
|
+
merror!(400, "Those emails do not belong to members of #{org.name}.") if memberships.empty?
|
|
166
|
+
memberships.update(membership_role_id: new_role.id)
|
|
167
|
+
roll_back_if_no_admins!(org)
|
|
168
|
+
message = "Success! These users have now been assigned the role of #{new_role.name} in #{org.name}."
|
|
169
|
+
status 200
|
|
170
|
+
present_collection memberships, with: Webhookdb::API::OrganizationMembershipEntity, message:
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
desc "Allow organization admin to change the name of the organization"
|
|
175
|
+
params do
|
|
176
|
+
optional :name, type: String, prompt: "Enter the new organization name:"
|
|
177
|
+
end
|
|
178
|
+
post :rename do
|
|
179
|
+
customer = current_customer
|
|
180
|
+
org = lookup_org!
|
|
181
|
+
ensure_admin!
|
|
182
|
+
customer.db.transaction do
|
|
183
|
+
prev_name = org.name
|
|
184
|
+
org.name = params[:name]
|
|
185
|
+
org.save_changes
|
|
186
|
+
status 200
|
|
187
|
+
present org, with: Webhookdb::API::OrganizationEntity,
|
|
188
|
+
message: "The organization '#{org.key}' has been renamed from '#{prev_name}' to '#{org.name}'."
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
desc "Request closure of an organization"
|
|
193
|
+
post :close do
|
|
194
|
+
org = lookup_org!
|
|
195
|
+
c = current_customer
|
|
196
|
+
ensure_admin!(org, customer: c)
|
|
197
|
+
Webhookdb::DeveloperAlert.new(
|
|
198
|
+
subsystem: "Close Account",
|
|
199
|
+
emoji: ":no_pedestrians:",
|
|
200
|
+
fallback: "Org #{org.key} requested removal",
|
|
201
|
+
fields: [
|
|
202
|
+
{title: "Org Key", value: org.key, short: true},
|
|
203
|
+
{title: "Org Name", value: org.name, short: true},
|
|
204
|
+
{title: "Customer", value: "(#{c.id}) #{c.email}", short: false},
|
|
205
|
+
],
|
|
206
|
+
).emit
|
|
207
|
+
step = Webhookdb::Replicator::StateMachineStep.new.completed
|
|
208
|
+
step.output = "Thanks! We've received the request to close your #{org.name} organization. " \
|
|
209
|
+
"We'll be in touch within 2 business days confirming removal."
|
|
210
|
+
status 200
|
|
211
|
+
present step, with: Webhookdb::API::StateMachineEntity
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
desc "Creates a new organization and adds current customer as a member."
|
|
216
|
+
params do
|
|
217
|
+
optional :name, type: String, prompt: "Enter the name of the organization:"
|
|
218
|
+
end
|
|
219
|
+
post :create do
|
|
220
|
+
customer = current_customer
|
|
221
|
+
customer.db.transaction do
|
|
222
|
+
new_org = Webhookdb::Organization.create_if_unique(name: params[:name])
|
|
223
|
+
merror!(400, "An organization with that name already exists.") if new_org.nil?
|
|
224
|
+
new_org.billing_email = customer.email
|
|
225
|
+
new_org.save_changes
|
|
226
|
+
mem = new_org.add_membership(customer:, membership_role: Webhookdb::Role.admin_role, verified: true)
|
|
227
|
+
customer.replace_default_membership(mem)
|
|
228
|
+
message = "Organization created with identifier '#{new_org.key}'.\n" \
|
|
229
|
+
"Use `webhookdb org invite` to invite members to #{new_org.name}."
|
|
230
|
+
status 200
|
|
231
|
+
present new_org, with: Webhookdb::API::OrganizationEntity, message:
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
desc "Allows user to verify membership in an organization with an invitation code."
|
|
236
|
+
params do
|
|
237
|
+
optional :invitation_code, type: String, prompt: "Enter the invitation code:"
|
|
238
|
+
end
|
|
239
|
+
post :join do
|
|
240
|
+
customer = current_customer
|
|
241
|
+
customer.db.transaction do
|
|
242
|
+
membership = customer.invited_memberships_dataset[invitation_code: params[:invitation_code]]
|
|
243
|
+
merror!(400, "Looks like that invite code is invalid. Please try again.", alert: true) if membership.nil?
|
|
244
|
+
membership.verified = true
|
|
245
|
+
membership.invitation_code = ""
|
|
246
|
+
membership.save_changes
|
|
247
|
+
customer.replace_default_membership(membership)
|
|
248
|
+
message = "Congratulations! You are now a member of #{membership.organization_name}."
|
|
249
|
+
status 200
|
|
250
|
+
present membership, with: Webhookdb::API::OrganizationMembershipEntity, message:
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/api"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::API::Replay < Webhookdb::API::V1
|
|
6
|
+
DEFAULT_INTERVAL = 1.hour
|
|
7
|
+
|
|
8
|
+
resource :organizations do
|
|
9
|
+
route_param :org_identifier, type: String do
|
|
10
|
+
resource :replay do
|
|
11
|
+
params do
|
|
12
|
+
optional :before, type: Time
|
|
13
|
+
optional :after, type: Time
|
|
14
|
+
optional :hours, type: Integer
|
|
15
|
+
optional :service_integration_identifier
|
|
16
|
+
end
|
|
17
|
+
post do
|
|
18
|
+
current_customer
|
|
19
|
+
org = lookup_org!
|
|
20
|
+
cutoff_lower = params[:after]
|
|
21
|
+
cutoff_upper = params[:before]
|
|
22
|
+
interval = params.fetch(:hours, 0).positive? ? params[:hours].hours : DEFAULT_INTERVAL
|
|
23
|
+
if cutoff_lower.nil? && cutoff_upper.nil?
|
|
24
|
+
cutoff_upper = Time.now.utc
|
|
25
|
+
cutoff_lower = cutoff_upper - interval
|
|
26
|
+
elsif cutoff_lower && cutoff_upper
|
|
27
|
+
nil
|
|
28
|
+
elsif cutoff_upper
|
|
29
|
+
cutoff_lower = cutoff_upper - interval
|
|
30
|
+
elsif cutoff_lower
|
|
31
|
+
# Add an offset so that cutoff_upper is in the same timezone as cutoff_lower
|
|
32
|
+
cutoff_upper = cutoff_lower + (Time.now - cutoff_lower)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
old_age_cutoff = Time.now - Webhookdb::LoggedWebhook.maximum_replay_history_hours.hours
|
|
36
|
+
merror!(400, "Webhooks older than #{old_age_cutoff.utc} cannot be replayed.") if cutoff_upper < old_age_cutoff
|
|
37
|
+
|
|
38
|
+
max_replay_hours = Webhookdb::LoggedWebhook.maximum_replay_interval_hours
|
|
39
|
+
if (cutoff_upper - cutoff_lower) > max_replay_hours.hours
|
|
40
|
+
merror!(400, "The maximum webhook replay interval is #{max_replay_hours} hours.")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
ds = Webhookdb::LoggedWebhook.where(organization_id: org.id).
|
|
44
|
+
where { inserted_at >= cutoff_lower }.
|
|
45
|
+
where { inserted_at <= cutoff_upper }.
|
|
46
|
+
where(truncated_at: nil).select(:id)
|
|
47
|
+
if params[:service_integration_identifier].present?
|
|
48
|
+
sint = lookup_service_integration!(org, params[:service_integration_identifier])
|
|
49
|
+
ds = ds.where(service_integration_opaque_id: sint.opaque_id)
|
|
50
|
+
end
|
|
51
|
+
replayed = 0
|
|
52
|
+
ds.paged_each do |lw|
|
|
53
|
+
lw.replay_async
|
|
54
|
+
replayed += 1
|
|
55
|
+
end
|
|
56
|
+
s = replayed == 1 ? "" : "s"
|
|
57
|
+
message = "Replaying #{replayed} webhook#{s} between #{cutoff_lower.iso8601} and #{cutoff_upper.iso8601}."
|
|
58
|
+
status 200
|
|
59
|
+
present({}, with: Webhookdb::API::BaseEntity, message:)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|