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,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/configurable"
|
|
4
|
+
require "warden"
|
|
5
|
+
|
|
6
|
+
class Webhookdb::Service::Auth
|
|
7
|
+
include Appydays::Configurable
|
|
8
|
+
|
|
9
|
+
class PasswordStrategy < Warden::Strategies::Base
|
|
10
|
+
def valid?
|
|
11
|
+
params["password"] && params["email"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def authenticate!
|
|
15
|
+
customer = self.lookup_customer
|
|
16
|
+
success!(customer) if customer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected def lookup_customer
|
|
20
|
+
customer = Webhookdb::Customer.with_email(params["email"].strip)
|
|
21
|
+
if customer.nil?
|
|
22
|
+
fail!("No customer with that email")
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
return customer if customer.authenticate(params["password"])
|
|
26
|
+
fail!("Incorrect password")
|
|
27
|
+
return nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class AdminPasswordStrategy < PasswordStrategy
|
|
32
|
+
def authenticate!
|
|
33
|
+
return unless (customer = self.lookup_customer)
|
|
34
|
+
unless customer.admin?
|
|
35
|
+
fail!
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
success!(customer)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Create the middleware for a Warden auth failure.
|
|
43
|
+
# Is not a 'normal' Rack middleware, which normally accepts 'app' in the initializer and has
|
|
44
|
+
# 'call' as an instance method.
|
|
45
|
+
# See https://github.com/wardencommunity/warden/wiki/Setup
|
|
46
|
+
class FailureApp
|
|
47
|
+
def self.call(env)
|
|
48
|
+
warden_opts = env.fetch("warden.options", {})
|
|
49
|
+
msg = warden_opts[:message] || env["webhookdb.authfailuremessage"] || "Unauthorized"
|
|
50
|
+
body = Webhookdb::Service.error_body(401, msg)
|
|
51
|
+
return 401, {"Content-Type" => "application/json"}, [body.to_json]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Middleware to use for Grape admin auth.
|
|
56
|
+
# See https://github.com/ruby-grape/grape#register-custom-middleware-for-authentication
|
|
57
|
+
# NOTE: Callers can use auth(nil) to disable auth for specific endpoints.
|
|
58
|
+
class Admin
|
|
59
|
+
def initialize(app, *_args)
|
|
60
|
+
@app = app
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call(env)
|
|
64
|
+
return @app.call(env) if Skip.skip?(@app)
|
|
65
|
+
warden = env["warden"]
|
|
66
|
+
customer = warden.authenticate!(scope: :admin)
|
|
67
|
+
|
|
68
|
+
unless customer.admin?
|
|
69
|
+
body = Webhookdb::Service.error_body(401, "Unauthorized")
|
|
70
|
+
return 401, {"Content-Type" => "application/json"}, [body.to_json]
|
|
71
|
+
end
|
|
72
|
+
return @app.call(env)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Custom auth strategies can check if their child app is a :skip auth,
|
|
77
|
+
# and if so, skip auth. Allows unauthed endpoints under authed endpoints.
|
|
78
|
+
class Skip
|
|
79
|
+
def self.skip?(app)
|
|
80
|
+
return app.options[:type] == :skip
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def initialize(app, *_args)
|
|
84
|
+
@app = app
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def call(env)
|
|
88
|
+
return @app.call(env)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Warden::Manager.serialize_into_session(&:id)
|
|
93
|
+
Warden::Manager.serialize_from_session { |id| Webhookdb::Customer[id] }
|
|
94
|
+
|
|
95
|
+
Warden::Strategies.add(:password, PasswordStrategy)
|
|
96
|
+
Warden::Strategies.add(:admin_password, AdminPasswordStrategy)
|
|
97
|
+
|
|
98
|
+
# Restore the /unauthenticated route to what it originally was.
|
|
99
|
+
# This is an API, not a rendered app...
|
|
100
|
+
Warden::Manager.before_failure do |env, opts|
|
|
101
|
+
env["PATH_INFO"] = opts[:attempted_path]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.add_warden_middleware(builder)
|
|
105
|
+
builder.use Warden::Manager do |manager|
|
|
106
|
+
# manager.default_strategies :password
|
|
107
|
+
manager.failure_app = FailureApp
|
|
108
|
+
|
|
109
|
+
manager.scope_defaults(:customer, strategies: [:password])
|
|
110
|
+
manager.scope_defaults(:admin, strategies: [:admin_password])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class Impersonation
|
|
115
|
+
attr_reader :admin_customer, :warden
|
|
116
|
+
|
|
117
|
+
def initialize(warden)
|
|
118
|
+
@warden = warden
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def is?
|
|
122
|
+
return false unless self.warden.authenticated?(:admin)
|
|
123
|
+
return self.warden.session(:admin)["impersonating"].present?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def on(target_customer)
|
|
127
|
+
self.warden.session(:admin)["impersonating"] = target_customer.id
|
|
128
|
+
self.warden.logout(:customer)
|
|
129
|
+
self.warden.set_user(target_customer, scope: :customer)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def off(admin_customer)
|
|
133
|
+
self.warden.logout(:customer)
|
|
134
|
+
self.warden.session(:admin).delete("impersonating")
|
|
135
|
+
self.warden.set_user(admin_customer, scope: :customer)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appydays/loggable"
|
|
4
|
+
require "grape"
|
|
5
|
+
|
|
6
|
+
require "webhookdb/service" unless defined?(Webhookdb::Service)
|
|
7
|
+
|
|
8
|
+
class Webhookdb::Service::Collection
|
|
9
|
+
extend Webhookdb::MethodUtilities
|
|
10
|
+
|
|
11
|
+
singleton_attr_reader :collection_entity_cache
|
|
12
|
+
@collection_entity_cache = {}
|
|
13
|
+
|
|
14
|
+
attr_reader :current_page, :items, :page_count, :total_count, :last_page
|
|
15
|
+
|
|
16
|
+
def self.from_dataset(ds)
|
|
17
|
+
if ds.respond_to?(:current_page)
|
|
18
|
+
return self.new(
|
|
19
|
+
ds.all,
|
|
20
|
+
current_page: ds.current_page,
|
|
21
|
+
page_count: ds.page_count,
|
|
22
|
+
total_count: ds.pagination_record_count,
|
|
23
|
+
last_page: ds.last_page?,
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
return self.from_array(ds.all)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.from_array(array)
|
|
30
|
+
return self.new(array, current_page: 1, page_count: 1, total_count: array.size, last_page: true)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(items, current_page:, page_count:, total_count:, last_page:)
|
|
34
|
+
@items = items
|
|
35
|
+
@current_page = current_page
|
|
36
|
+
@page_count = page_count
|
|
37
|
+
@last_page = last_page
|
|
38
|
+
@total_count = total_count
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def last_page?
|
|
42
|
+
return @last_page
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def more?
|
|
46
|
+
return !@last_page
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module Helpers
|
|
50
|
+
def present_collection(collection, opts={})
|
|
51
|
+
item_entity = opts.delete(:with) || opts.delete(:using)
|
|
52
|
+
if !item_entity.nil? && (item_entity < Webhookdb::Service::Entities::Base)
|
|
53
|
+
# If we have a real entity, we only want message on the top level, not any nested
|
|
54
|
+
item_entity = Class.new(item_entity) do
|
|
55
|
+
unexpose :message
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
unless (collection_entity = Webhookdb::Service::Collection.collection_entity_cache[item_entity])
|
|
59
|
+
collection_entity = Class.new(Webhookdb::Service::Entities::Base) do
|
|
60
|
+
define_method(:object_type) do
|
|
61
|
+
"list"
|
|
62
|
+
end
|
|
63
|
+
expose :items, using: item_entity
|
|
64
|
+
expose :current_page
|
|
65
|
+
expose :page_count
|
|
66
|
+
expose :total_count
|
|
67
|
+
expose :more?, as: :has_more
|
|
68
|
+
expose :message do |_instance, options|
|
|
69
|
+
options[:message] || ""
|
|
70
|
+
end
|
|
71
|
+
expose :display_headers do |_, _|
|
|
72
|
+
item_entity.respond_to?(:display_headers) ? item_entity.send(:display_headers) : []
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
Webhookdb::Service::Collection.collection_entity_cache[item_entity] = collection_entity
|
|
76
|
+
end
|
|
77
|
+
opts[:with] = collection_entity
|
|
78
|
+
|
|
79
|
+
wrapped =
|
|
80
|
+
if collection.respond_to?(:dataset) || collection.is_a?(Sequel::Dataset)
|
|
81
|
+
Webhookdb::Service::Collection.from_dataset(collection)
|
|
82
|
+
elsif collection.is_a?(Webhookdb::Service::Collection)
|
|
83
|
+
collection
|
|
84
|
+
else
|
|
85
|
+
Webhookdb::Service::Collection.from_array(collection)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
present wrapped, opts
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "grape_entity"
|
|
4
|
+
|
|
5
|
+
module Webhookdb::Service::Entities
|
|
6
|
+
class Money < Grape::Entity
|
|
7
|
+
expose :cents
|
|
8
|
+
expose :currency do |obj|
|
|
9
|
+
obj.currency.iso_code
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class TimeRange < Grape::Entity
|
|
14
|
+
expose :begin, as: :start
|
|
15
|
+
expose :end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Base < Grape::Entity
|
|
19
|
+
extend Webhookdb::MethodUtilities
|
|
20
|
+
|
|
21
|
+
expose :object_type, as: :object, unless: ->(_, _) { self.object_type.nil? }
|
|
22
|
+
|
|
23
|
+
# Override this on entities that are addressable on their own
|
|
24
|
+
def object_type
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.delegate_to(*names, safe: false, safe_with_default: nil)
|
|
29
|
+
return lambda do |instance|
|
|
30
|
+
names.reduce(instance) do |memo, name|
|
|
31
|
+
memo.send(name)
|
|
32
|
+
rescue NoMethodError => e
|
|
33
|
+
raise e unless safe || safe_with_default
|
|
34
|
+
return safe_with_default
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.timezone(*lookup_path, field: nil)
|
|
40
|
+
return lambda do |instance, opts|
|
|
41
|
+
field ||= opts[:attr_path].last
|
|
42
|
+
tz = lookup_path.reduce(instance) do |memo, name|
|
|
43
|
+
memo.send(name)
|
|
44
|
+
rescue NoMethodError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
t = instance.send(field)
|
|
48
|
+
if tz.blank?
|
|
49
|
+
t
|
|
50
|
+
else
|
|
51
|
+
tz = tz.timezone if tz.respond_to?(:timezone)
|
|
52
|
+
tz = tz.time_zone if tz.respond_to?(:time_zone)
|
|
53
|
+
t.in_time_zone(tz).iso8601
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
expose :message do |_instance, options|
|
|
59
|
+
options[:message] || ""
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Image < Base
|
|
64
|
+
expose :url
|
|
65
|
+
expose :alt
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class CurrentCustomer < Base
|
|
69
|
+
expose :id
|
|
70
|
+
expose :created_at
|
|
71
|
+
expose :email
|
|
72
|
+
expose :name
|
|
73
|
+
expose :roles do |instance|
|
|
74
|
+
instance.roles.map(&:name)
|
|
75
|
+
end
|
|
76
|
+
expose :impersonated do |_instance, options|
|
|
77
|
+
Webhookdb::Service::Auth::Impersonation.new(options[:env]["warden"]).is?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Add an 'etag' field to the rendered entity.
|
|
82
|
+
# This should only be used on the root entity, and entities with etags should not be nested.
|
|
83
|
+
# Usage:
|
|
84
|
+
#
|
|
85
|
+
# class DashboardEntity < BaseEntity
|
|
86
|
+
# prepend Webhookdb::Service::Entities::EtaggedMixin
|
|
87
|
+
# expose :my_field
|
|
88
|
+
# end
|
|
89
|
+
module EtaggedMixin
|
|
90
|
+
def to_json(*)
|
|
91
|
+
serialized = super
|
|
92
|
+
raise TypeError, "EtaggedMixin can only be used for object entities" unless serialized[-1] == "}"
|
|
93
|
+
etag = Digest::MD5.hexdigest(Webhookdb::COMMIT.to_s + serialized)
|
|
94
|
+
return serialized[...-1] + ",\"etag\":\"#{etag}\"}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
require "grape"
|
|
6
|
+
|
|
7
|
+
require "webhookdb/service" unless defined?(Webhookdb::Service)
|
|
8
|
+
require "webhookdb/service/collection"
|
|
9
|
+
|
|
10
|
+
# A collection of helper functions that can be included
|
|
11
|
+
module Webhookdb::Service::Helpers
|
|
12
|
+
extend Grape::API::Helpers
|
|
13
|
+
include Webhookdb::Service::Collection::Helpers
|
|
14
|
+
|
|
15
|
+
def logger
|
|
16
|
+
return Webhookdb::Service.logger
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Return the currently-authenticated user,
|
|
20
|
+
# or respond with a 401 if there is no authenticated user.
|
|
21
|
+
def current_customer
|
|
22
|
+
return _check_customer_deleted(env["warden"].authenticate!(scope: :customer), admin_customer?)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Return the currently-authenticated user,
|
|
26
|
+
# or respond nil if there is no authenticated user.
|
|
27
|
+
def current_customer?
|
|
28
|
+
return _check_customer_deleted(env["warden"].user(scope: :customer), admin_customer?)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def admin_customer
|
|
32
|
+
return _check_customer_deleted(env["warden"].authenticate!(scope: :admin), nil)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def admin_customer?
|
|
36
|
+
return _check_customer_deleted(env["warden"].authenticate(scope: :admin), nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def authenticate!
|
|
40
|
+
warden = env["warden"]
|
|
41
|
+
user = warden.authenticate!(scope: :customer)
|
|
42
|
+
warden.set_user(user, scope: :admin) if user.admin?
|
|
43
|
+
return user
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Handle denying authentication if the given user cannot auth.
|
|
47
|
+
# That is:
|
|
48
|
+
# - if we have an admin, but they should not be (deleted or missing role), throw unauthed error.
|
|
49
|
+
# - if current user is nil, return nil, since the caller can handle it.
|
|
50
|
+
# - if current user is deleted and there is no admin, throw unauthed error.
|
|
51
|
+
# - if current user is deleted and admin is deleted, throw unauthed error.
|
|
52
|
+
# - otherwise, return current user.
|
|
53
|
+
#
|
|
54
|
+
# The scenarios this covers are:
|
|
55
|
+
# - Normal users cannot auth if deleted.
|
|
56
|
+
# - Admins can sudo deleted users, and current_customer still works.
|
|
57
|
+
# - Deleted admins cannot auth or get their sudo'ed user.
|
|
58
|
+
#
|
|
59
|
+
# NOTE: It is safe to throw unauthed errors for deleted users-
|
|
60
|
+
# this does not expose whether a user exists or not,
|
|
61
|
+
# because the only way to call this is via cookies,
|
|
62
|
+
# and cookies are encrypted. So it is impossible to force requests
|
|
63
|
+
# trying to auth/check auth for a user without knowing the secret.
|
|
64
|
+
def _check_customer_deleted(user, potential_admin)
|
|
65
|
+
return nil if user.nil?
|
|
66
|
+
if potential_admin && (potential_admin.soft_deleted? || !potential_admin.roles.include?(Webhookdb::Role.admin_role))
|
|
67
|
+
delete_session_cookies
|
|
68
|
+
unauthenticated!
|
|
69
|
+
end
|
|
70
|
+
if user.soft_deleted? && potential_admin.nil?
|
|
71
|
+
delete_session_cookies
|
|
72
|
+
unauthenticated!
|
|
73
|
+
end
|
|
74
|
+
return user
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def delete_session_cookies
|
|
78
|
+
# Nope, cannot do this through Warden easily.
|
|
79
|
+
# And really we should have server-based sessions we can expire,
|
|
80
|
+
# but in the meantime, stomp on the cookie hard.
|
|
81
|
+
options = env[Rack::RACK_SESSION_OPTIONS]
|
|
82
|
+
options[:drop] = true
|
|
83
|
+
# Rack sends a cookie with an empty session, but let's tell the browser to actually delete the cookie.
|
|
84
|
+
cookies.delete(Webhookdb::Service::SESSION_COOKIE, domain: options[:domain], path: options[:path])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_customer(customer)
|
|
88
|
+
warden = env["warden"]
|
|
89
|
+
warden.set_user(customer, scope: :customer)
|
|
90
|
+
warden.set_user(customer, scope: :admin) if customer.admin?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def current_session_id
|
|
94
|
+
return env["rack.session"].id
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_role!(customer, role_name)
|
|
98
|
+
has_role = customer.roles.find { |r| r.name == role_name }
|
|
99
|
+
return if has_role
|
|
100
|
+
role_exists = !Webhookdb::Role.where(name: role_name).empty?
|
|
101
|
+
raise "The role '#{role_name}' does not exist so cannot be checked. You need to create it first." unless role_exists
|
|
102
|
+
permission_error!("Sorry, this action is unavailable.")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def merror!(status, message, code: nil, more: {}, headers: {}, rollback_db: nil, alert: false)
|
|
106
|
+
header "Content-Type", "application/json"
|
|
107
|
+
body = Webhookdb::Service.error_body(status, message, code:, more:)
|
|
108
|
+
if alert
|
|
109
|
+
Sentry.with_scope do |scope|
|
|
110
|
+
scope&.set_extras(**body)
|
|
111
|
+
Sentry.capture_message(message)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
if rollback_db
|
|
115
|
+
Webhookdb::Postgres.defer_after_rollback(rollback_db) do
|
|
116
|
+
error!(body, status, headers)
|
|
117
|
+
end
|
|
118
|
+
raise Sequel::Rollback
|
|
119
|
+
else
|
|
120
|
+
error!(body, status, headers)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def unauthenticated!
|
|
125
|
+
merror!(401, "Unauthenticated", code: "unauthenticated")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def unauthenticated_with_message!(msg)
|
|
129
|
+
env["webhookdb.authfailuremessage"] = msg
|
|
130
|
+
unauthenticated!
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def forbidden!
|
|
134
|
+
merror!(403, "Forbidden", code: "forbidden")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def not_found!
|
|
138
|
+
merror!(404, "Not Found", code: "not_found")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def permission_error!(message)
|
|
142
|
+
merror!(403, message, code: "permission_check")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Raise a 400 error for unstructured validation.
|
|
146
|
+
# @param errors [Array<String>,String] Error messages, like 'password is invalid'.
|
|
147
|
+
# @param message [String] If not given, build it from the errors list.
|
|
148
|
+
def invalid!(errors, message: nil)
|
|
149
|
+
errors = [errors] unless errors.respond_to?(:to_ary)
|
|
150
|
+
message ||= errors.join(", ").upcase_first
|
|
151
|
+
merror!(400, message, code: "validation_error", more: {errors:, field_errors: {}})
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Raise a 400 error for structured validation.
|
|
155
|
+
# @param field_errors [Hash<String, Array<String>>] If errors are tied to fields,
|
|
156
|
+
# this is a hash where the key is the field name, and the value is an array of all validation messages.
|
|
157
|
+
# For example, {password: ['is invalid']}
|
|
158
|
+
# @param message [String] If not given, build it from the errors list.
|
|
159
|
+
def invalid_fields!(field_errors, message: nil)
|
|
160
|
+
errors = field_errors.map { |field, field_errs| field_errs.map { |e| "#{field} #{e}" } }.flatten
|
|
161
|
+
message ||= errors.join(", ").upcase_first
|
|
162
|
+
merror!(400, message, code: "validation_error", more: {errors:, field_errors:})
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def endpoint_removed!
|
|
166
|
+
merror!(
|
|
167
|
+
403,
|
|
168
|
+
"Sorry, this endpoint has been removed. Run `webhookdb update` to upgrade your CLI, " \
|
|
169
|
+
"or file a ticket at #{Webhookdb.oss_repo_url} for help.",
|
|
170
|
+
code: "endpoint_removed",
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def search_param_to_sql(params, column, param: :search)
|
|
175
|
+
search = params[param]&.strip
|
|
176
|
+
return nil if search.blank? || search == "*"
|
|
177
|
+
term = "%#{search.strip}%"
|
|
178
|
+
return Sequel.ilike(column, term)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
### If +object+ is valid, save and return it.
|
|
182
|
+
### If not, call invalid! witht the validation errors.
|
|
183
|
+
def save_or_error!(object)
|
|
184
|
+
if object.valid?
|
|
185
|
+
object.save_changes
|
|
186
|
+
return object
|
|
187
|
+
else
|
|
188
|
+
invalid_fields!(object.errors.to_h)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def paginate(dataset, params)
|
|
193
|
+
return dataset.paginate(params[:page], params[:per_page])
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def order(dataset, params)
|
|
197
|
+
expr = params[:order_direction] == :asc ? Sequel.asc(params[:order_by]) : Sequel.desc(params[:order_by])
|
|
198
|
+
return dataset.order(expr, Sequel.desc(:id))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def use_http_expires_caching(expiration)
|
|
202
|
+
return unless Webhookdb::Service.endpoint_caching
|
|
203
|
+
header "Cache-Control", "public"
|
|
204
|
+
header "Expires", expiration.from_now.httpdate
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Set the provided, declared/valid parameters in params on model.
|
|
208
|
+
# Because Grape's `declared()` function *adds* parameters that are declared-but-not-provided,
|
|
209
|
+
# and its `params` value includes provided-but-not-declared entries,
|
|
210
|
+
# the fields we set are the intersection of the two.
|
|
211
|
+
def set_declared(model, params, ignore: [:id])
|
|
212
|
+
# If .to_h is used (rather than Grape's 'params' which is HashWithIndifferentAccess),
|
|
213
|
+
# the keys may be strings. We need to deep symbolize since nested hashes get to_h with 'symbolize_keys'.
|
|
214
|
+
params = params.deep_symbolize_keys
|
|
215
|
+
decl = declared_and_provided_params(params, exclude: ignore)
|
|
216
|
+
ignore.each { |k| decl.delete(k) }
|
|
217
|
+
decl.delete_if { |k| !params.key?(k) }
|
|
218
|
+
model.set(decl)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def declared_and_provided_params(params, exclude: [])
|
|
222
|
+
decl = declared(params)
|
|
223
|
+
exclude.each { |k| decl.delete(k) }
|
|
224
|
+
decl.delete_if { |k| !params.key?(k) }
|
|
225
|
+
return decl
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
params :money do
|
|
229
|
+
requires :cents, type: Integer
|
|
230
|
+
optional :currency, type: String, default: "USD"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
params :time_range do
|
|
234
|
+
requires :start, as: :begin, type: Time
|
|
235
|
+
requires :end, type: Time
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
params :pagination do
|
|
239
|
+
optional :page, type: Integer, default: 1
|
|
240
|
+
optional :per_page, type: Integer, default: 100
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
params :searchable do
|
|
244
|
+
optional :search, type: String
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
params :order do |options|
|
|
248
|
+
optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by]
|
|
249
|
+
optional :order, type: Symbol, values: [:asc, :desc], default: options[:default_order]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
params :ordering do |options|
|
|
253
|
+
default_order_by = options[:default] || :created_at
|
|
254
|
+
order_by_values = options[:values] || options[:model]&.columns
|
|
255
|
+
raise "Must provide :values or :model for possible orderings" unless order_by_values
|
|
256
|
+
optional :order_by, type: Symbol, values: order_by_values, default: default_order_by
|
|
257
|
+
optional :order_direction, type: Symbol, values: [:asc, :desc], default: :desc
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
params :address do
|
|
261
|
+
optional :address1, type: String, allow_blank: false
|
|
262
|
+
optional :address2, type: String
|
|
263
|
+
optional :city, type: String, allow_blank: false
|
|
264
|
+
optional :state_or_province, type: String, allow_blank: false
|
|
265
|
+
optional :postal_code, type: String, allow_blank: false
|
|
266
|
+
all_or_none_of :address1, :city, :state_or_province, :postal_code
|
|
267
|
+
optional :lat, type: Float
|
|
268
|
+
optional :lng, type: Float
|
|
269
|
+
end
|
|
270
|
+
end
|