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.
Files changed (364) hide show
  1. checksums.yaml +7 -0
  2. data/data/messages/layouts/blank.email.liquid +10 -0
  3. data/data/messages/layouts/minimal.email.liquid +28 -0
  4. data/data/messages/layouts/standard.email.liquid +28 -0
  5. data/data/messages/partials/button.liquid +15 -0
  6. data/data/messages/partials/environment_banner.liquid +9 -0
  7. data/data/messages/partials/footer.liquid +22 -0
  8. data/data/messages/partials/greeting.liquid +3 -0
  9. data/data/messages/partials/logo_header.liquid +18 -0
  10. data/data/messages/partials/signoff.liquid +1 -0
  11. data/data/messages/styles/v1.liquid +346 -0
  12. data/data/messages/templates/errors/icalendar_fetch.email.liquid +29 -0
  13. data/data/messages/templates/invite.email.liquid +15 -0
  14. data/data/messages/templates/new_customer.email.liquid +24 -0
  15. data/data/messages/templates/org_database_migration_finished.email.liquid +7 -0
  16. data/data/messages/templates/org_database_migration_started.email.liquid +9 -0
  17. data/data/messages/templates/specs/_field_partial.liquid +1 -0
  18. data/data/messages/templates/specs/basic.email.liquid +2 -0
  19. data/data/messages/templates/specs/basic.fake.liquid +1 -0
  20. data/data/messages/templates/specs/with_field.email.liquid +2 -0
  21. data/data/messages/templates/specs/with_field.fake.liquid +1 -0
  22. data/data/messages/templates/specs/with_include.email.liquid +2 -0
  23. data/data/messages/templates/specs/with_partial.email.liquid +1 -0
  24. data/data/messages/templates/verification.email.liquid +14 -0
  25. data/data/messages/templates/verification.sms.liquid +1 -0
  26. data/data/messages/web/install-customer-login.liquid +48 -0
  27. data/data/messages/web/install-error.liquid +17 -0
  28. data/data/messages/web/install-success.liquid +35 -0
  29. data/data/messages/web/install.liquid +20 -0
  30. data/data/messages/web/partials/footer.liquid +4 -0
  31. data/data/messages/web/partials/form_error.liquid +1 -0
  32. data/data/messages/web/partials/header.liquid +3 -0
  33. data/data/messages/web/styles.liquid +134 -0
  34. data/data/windows_tz.txt +461 -0
  35. data/db/migrations/001_testing_pixies.rb +13 -0
  36. data/db/migrations/002_initial.rb +132 -0
  37. data/db/migrations/003_ux_overhaul.rb +20 -0
  38. data/db/migrations/004_incremental_backfill.rb +9 -0
  39. data/db/migrations/005_log_webhooks.rb +24 -0
  40. data/db/migrations/006_generalize_roles.rb +29 -0
  41. data/db/migrations/007_org_dns.rb +12 -0
  42. data/db/migrations/008_webhook_subscriptions.rb +19 -0
  43. data/db/migrations/009_nonunique_stripe_subscription_customer.rb +16 -0
  44. data/db/migrations/010_drop_integration_soft_delete.rb +14 -0
  45. data/db/migrations/011_webhook_subscriptions_created_at.rb +10 -0
  46. data/db/migrations/012_webhook_subscriptions_created_by.rb +9 -0
  47. data/db/migrations/013_default_org_membership.rb +30 -0
  48. data/db/migrations/014_webhook_subscription_deliveries.rb +26 -0
  49. data/db/migrations/015_dependent_integrations.rb +9 -0
  50. data/db/migrations/016_encrypted_columns.rb +9 -0
  51. data/db/migrations/017_skip_verification.rb +9 -0
  52. data/db/migrations/018_sync_targets.rb +25 -0
  53. data/db/migrations/019_org_schema.rb +9 -0
  54. data/db/migrations/020_org_database_migrations.rb +25 -0
  55. data/db/migrations/021_no_default_org_schema.rb +14 -0
  56. data/db/migrations/022_database_document.rb +15 -0
  57. data/db/migrations/023_sync_target_schema.rb +9 -0
  58. data/db/migrations/024_org_semaphore_jobs.rb +9 -0
  59. data/db/migrations/025_integration_backfill_cursor.rb +9 -0
  60. data/db/migrations/026_undo_integration_backfill_cursor.rb +9 -0
  61. data/db/migrations/027_sync_target_http_sync.rb +12 -0
  62. data/db/migrations/028_logged_webhook_path.rb +24 -0
  63. data/db/migrations/029_encrypt_columns.rb +97 -0
  64. data/db/migrations/030_org_sync_target_timeout.rb +9 -0
  65. data/db/migrations/031_org_max_query_rows.rb +9 -0
  66. data/db/migrations/032_remove_db_defaults.rb +12 -0
  67. data/db/migrations/033_backfill_jobs.rb +26 -0
  68. data/db/migrations/034_backfill_job_criteria.rb +9 -0
  69. data/db/migrations/035_synchronous_backfill.rb +9 -0
  70. data/db/migrations/036_oauth.rb +26 -0
  71. data/db/migrations/037_oauth_used.rb +9 -0
  72. data/lib/amigo/durable_job.rb +416 -0
  73. data/lib/pry/clipboard.rb +111 -0
  74. data/lib/sequel/advisory_lock.rb +65 -0
  75. data/lib/webhookdb/admin.rb +4 -0
  76. data/lib/webhookdb/admin_api/auth.rb +36 -0
  77. data/lib/webhookdb/admin_api/customers.rb +63 -0
  78. data/lib/webhookdb/admin_api/database_documents.rb +20 -0
  79. data/lib/webhookdb/admin_api/entities.rb +66 -0
  80. data/lib/webhookdb/admin_api/message_deliveries.rb +61 -0
  81. data/lib/webhookdb/admin_api/roles.rb +15 -0
  82. data/lib/webhookdb/admin_api.rb +34 -0
  83. data/lib/webhookdb/aggregate_result.rb +63 -0
  84. data/lib/webhookdb/api/auth.rb +122 -0
  85. data/lib/webhookdb/api/connstr_auth.rb +36 -0
  86. data/lib/webhookdb/api/db.rb +188 -0
  87. data/lib/webhookdb/api/demo.rb +14 -0
  88. data/lib/webhookdb/api/entities.rb +198 -0
  89. data/lib/webhookdb/api/helpers.rb +253 -0
  90. data/lib/webhookdb/api/install.rb +296 -0
  91. data/lib/webhookdb/api/me.rb +53 -0
  92. data/lib/webhookdb/api/organizations.rb +254 -0
  93. data/lib/webhookdb/api/replay.rb +64 -0
  94. data/lib/webhookdb/api/service_integrations.rb +402 -0
  95. data/lib/webhookdb/api/services.rb +27 -0
  96. data/lib/webhookdb/api/stripe.rb +22 -0
  97. data/lib/webhookdb/api/subscriptions.rb +67 -0
  98. data/lib/webhookdb/api/sync_targets.rb +232 -0
  99. data/lib/webhookdb/api/system.rb +37 -0
  100. data/lib/webhookdb/api/webhook_subscriptions.rb +96 -0
  101. data/lib/webhookdb/api.rb +92 -0
  102. data/lib/webhookdb/apps.rb +93 -0
  103. data/lib/webhookdb/async/audit_logger.rb +38 -0
  104. data/lib/webhookdb/async/autoscaler.rb +84 -0
  105. data/lib/webhookdb/async/job.rb +18 -0
  106. data/lib/webhookdb/async/job_logger.rb +45 -0
  107. data/lib/webhookdb/async/scheduled_job.rb +18 -0
  108. data/lib/webhookdb/async.rb +142 -0
  109. data/lib/webhookdb/aws.rb +98 -0
  110. data/lib/webhookdb/backfill_job.rb +107 -0
  111. data/lib/webhookdb/backfiller.rb +107 -0
  112. data/lib/webhookdb/cloudflare.rb +39 -0
  113. data/lib/webhookdb/connection_cache.rb +177 -0
  114. data/lib/webhookdb/console.rb +71 -0
  115. data/lib/webhookdb/convertkit.rb +14 -0
  116. data/lib/webhookdb/crypto.rb +66 -0
  117. data/lib/webhookdb/customer/reset_code.rb +94 -0
  118. data/lib/webhookdb/customer.rb +347 -0
  119. data/lib/webhookdb/database_document.rb +72 -0
  120. data/lib/webhookdb/db_adapter/column_types.rb +37 -0
  121. data/lib/webhookdb/db_adapter/default_sql.rb +187 -0
  122. data/lib/webhookdb/db_adapter/pg.rb +96 -0
  123. data/lib/webhookdb/db_adapter/snowflake.rb +137 -0
  124. data/lib/webhookdb/db_adapter.rb +208 -0
  125. data/lib/webhookdb/dbutil.rb +92 -0
  126. data/lib/webhookdb/demo_mode.rb +100 -0
  127. data/lib/webhookdb/developer_alert.rb +51 -0
  128. data/lib/webhookdb/email_octopus.rb +21 -0
  129. data/lib/webhookdb/enumerable.rb +18 -0
  130. data/lib/webhookdb/fixtures/backfill_jobs.rb +72 -0
  131. data/lib/webhookdb/fixtures/customers.rb +65 -0
  132. data/lib/webhookdb/fixtures/database_documents.rb +27 -0
  133. data/lib/webhookdb/fixtures/faker.rb +41 -0
  134. data/lib/webhookdb/fixtures/logged_webhooks.rb +56 -0
  135. data/lib/webhookdb/fixtures/message_deliveries.rb +59 -0
  136. data/lib/webhookdb/fixtures/oauth_sessions.rb +24 -0
  137. data/lib/webhookdb/fixtures/organization_database_migrations.rb +37 -0
  138. data/lib/webhookdb/fixtures/organization_memberships.rb +54 -0
  139. data/lib/webhookdb/fixtures/organizations.rb +32 -0
  140. data/lib/webhookdb/fixtures/reset_codes.rb +23 -0
  141. data/lib/webhookdb/fixtures/service_integrations.rb +42 -0
  142. data/lib/webhookdb/fixtures/subscriptions.rb +33 -0
  143. data/lib/webhookdb/fixtures/sync_targets.rb +32 -0
  144. data/lib/webhookdb/fixtures/webhook_subscriptions.rb +35 -0
  145. data/lib/webhookdb/fixtures.rb +15 -0
  146. data/lib/webhookdb/formatting.rb +56 -0
  147. data/lib/webhookdb/front.rb +49 -0
  148. data/lib/webhookdb/github.rb +22 -0
  149. data/lib/webhookdb/google_calendar.rb +29 -0
  150. data/lib/webhookdb/heroku.rb +21 -0
  151. data/lib/webhookdb/http.rb +114 -0
  152. data/lib/webhookdb/icalendar.rb +17 -0
  153. data/lib/webhookdb/id.rb +17 -0
  154. data/lib/webhookdb/idempotency.rb +90 -0
  155. data/lib/webhookdb/increase.rb +42 -0
  156. data/lib/webhookdb/intercom.rb +23 -0
  157. data/lib/webhookdb/jobs/amigo_test_jobs.rb +118 -0
  158. data/lib/webhookdb/jobs/backfill.rb +32 -0
  159. data/lib/webhookdb/jobs/create_mirror_table.rb +18 -0
  160. data/lib/webhookdb/jobs/create_stripe_customer.rb +17 -0
  161. data/lib/webhookdb/jobs/customer_created_notify_internal.rb +22 -0
  162. data/lib/webhookdb/jobs/demo_mode_sync_data.rb +19 -0
  163. data/lib/webhookdb/jobs/deprecated_jobs.rb +19 -0
  164. data/lib/webhookdb/jobs/developer_alert_handle.rb +14 -0
  165. data/lib/webhookdb/jobs/durable_job_recheck_poller.rb +17 -0
  166. data/lib/webhookdb/jobs/emailer.rb +15 -0
  167. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +25 -0
  168. data/lib/webhookdb/jobs/icalendar_sync.rb +23 -0
  169. data/lib/webhookdb/jobs/logged_webhook_replay.rb +17 -0
  170. data/lib/webhookdb/jobs/logged_webhook_resilient_replay.rb +15 -0
  171. data/lib/webhookdb/jobs/message_dispatched.rb +16 -0
  172. data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +21 -0
  173. data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +21 -0
  174. data/lib/webhookdb/jobs/organization_database_migration_run.rb +24 -0
  175. data/lib/webhookdb/jobs/prepare_database_connections.rb +22 -0
  176. data/lib/webhookdb/jobs/process_webhook.rb +47 -0
  177. data/lib/webhookdb/jobs/renew_watch_channel.rb +24 -0
  178. data/lib/webhookdb/jobs/replication_migration.rb +24 -0
  179. data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +23 -0
  180. data/lib/webhookdb/jobs/scheduled_backfills.rb +77 -0
  181. data/lib/webhookdb/jobs/send_invite.rb +15 -0
  182. data/lib/webhookdb/jobs/send_test_webhook.rb +25 -0
  183. data/lib/webhookdb/jobs/send_webhook.rb +20 -0
  184. data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +16 -0
  185. data/lib/webhookdb/jobs/sync_target_run_sync.rb +38 -0
  186. data/lib/webhookdb/jobs/trim_logged_webhooks.rb +15 -0
  187. data/lib/webhookdb/jobs/webhook_resource_notify_integrations.rb +30 -0
  188. data/lib/webhookdb/jobs/webhook_subscription_delivery_attempt.rb +29 -0
  189. data/lib/webhookdb/jobs.rb +4 -0
  190. data/lib/webhookdb/json.rb +113 -0
  191. data/lib/webhookdb/liquid/expose.rb +27 -0
  192. data/lib/webhookdb/liquid/filters.rb +16 -0
  193. data/lib/webhookdb/liquid/liquification.rb +26 -0
  194. data/lib/webhookdb/liquid/partial.rb +12 -0
  195. data/lib/webhookdb/logged_webhook/resilient.rb +95 -0
  196. data/lib/webhookdb/logged_webhook.rb +194 -0
  197. data/lib/webhookdb/message/body.rb +25 -0
  198. data/lib/webhookdb/message/delivery.rb +127 -0
  199. data/lib/webhookdb/message/email_transport.rb +133 -0
  200. data/lib/webhookdb/message/fake_transport.rb +54 -0
  201. data/lib/webhookdb/message/liquid_drops.rb +29 -0
  202. data/lib/webhookdb/message/template.rb +89 -0
  203. data/lib/webhookdb/message/transport.rb +43 -0
  204. data/lib/webhookdb/message.rb +150 -0
  205. data/lib/webhookdb/messages/error_icalendar_fetch.rb +42 -0
  206. data/lib/webhookdb/messages/invite.rb +23 -0
  207. data/lib/webhookdb/messages/new_customer.rb +14 -0
  208. data/lib/webhookdb/messages/org_database_migration_finished.rb +23 -0
  209. data/lib/webhookdb/messages/org_database_migration_started.rb +24 -0
  210. data/lib/webhookdb/messages/specs.rb +57 -0
  211. data/lib/webhookdb/messages/verification.rb +23 -0
  212. data/lib/webhookdb/method_utilities.rb +82 -0
  213. data/lib/webhookdb/microsoft_calendar.rb +36 -0
  214. data/lib/webhookdb/nextpax.rb +14 -0
  215. data/lib/webhookdb/oauth/front.rb +58 -0
  216. data/lib/webhookdb/oauth/intercom.rb +58 -0
  217. data/lib/webhookdb/oauth/session.rb +24 -0
  218. data/lib/webhookdb/oauth.rb +80 -0
  219. data/lib/webhookdb/organization/alerting.rb +35 -0
  220. data/lib/webhookdb/organization/database_migration.rb +151 -0
  221. data/lib/webhookdb/organization/db_builder.rb +429 -0
  222. data/lib/webhookdb/organization.rb +506 -0
  223. data/lib/webhookdb/organization_membership.rb +58 -0
  224. data/lib/webhookdb/phone_number.rb +38 -0
  225. data/lib/webhookdb/plaid.rb +23 -0
  226. data/lib/webhookdb/platform.rb +27 -0
  227. data/lib/webhookdb/plivo.rb +52 -0
  228. data/lib/webhookdb/postgres/maintenance.rb +166 -0
  229. data/lib/webhookdb/postgres/model.rb +82 -0
  230. data/lib/webhookdb/postgres/model_utilities.rb +382 -0
  231. data/lib/webhookdb/postgres/testing_pixie.rb +16 -0
  232. data/lib/webhookdb/postgres/validations.rb +46 -0
  233. data/lib/webhookdb/postgres.rb +176 -0
  234. data/lib/webhookdb/postmark.rb +20 -0
  235. data/lib/webhookdb/redis.rb +35 -0
  236. data/lib/webhookdb/replicator/atom_single_feed_v1.rb +116 -0
  237. data/lib/webhookdb/replicator/aws_pricing_v1.rb +488 -0
  238. data/lib/webhookdb/replicator/base.rb +1185 -0
  239. data/lib/webhookdb/replicator/column.rb +482 -0
  240. data/lib/webhookdb/replicator/convertkit_broadcast_v1.rb +69 -0
  241. data/lib/webhookdb/replicator/convertkit_subscriber_v1.rb +200 -0
  242. data/lib/webhookdb/replicator/convertkit_tag_v1.rb +66 -0
  243. data/lib/webhookdb/replicator/convertkit_v1_mixin.rb +65 -0
  244. data/lib/webhookdb/replicator/docgen.rb +167 -0
  245. data/lib/webhookdb/replicator/email_octopus_campaign_v1.rb +84 -0
  246. data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +159 -0
  247. data/lib/webhookdb/replicator/email_octopus_event_v1.rb +244 -0
  248. data/lib/webhookdb/replicator/email_octopus_list_v1.rb +101 -0
  249. data/lib/webhookdb/replicator/fake.rb +453 -0
  250. data/lib/webhookdb/replicator/front_conversation_v1.rb +45 -0
  251. data/lib/webhookdb/replicator/front_marketplace_root_v1.rb +55 -0
  252. data/lib/webhookdb/replicator/front_message_v1.rb +45 -0
  253. data/lib/webhookdb/replicator/front_v1_mixin.rb +22 -0
  254. data/lib/webhookdb/replicator/github_issue_comment_v1.rb +58 -0
  255. data/lib/webhookdb/replicator/github_issue_v1.rb +83 -0
  256. data/lib/webhookdb/replicator/github_pull_v1.rb +84 -0
  257. data/lib/webhookdb/replicator/github_release_v1.rb +47 -0
  258. data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +250 -0
  259. data/lib/webhookdb/replicator/github_repository_event_v1.rb +45 -0
  260. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +465 -0
  261. data/lib/webhookdb/replicator/icalendar_event_v1.rb +334 -0
  262. data/lib/webhookdb/replicator/increase_account_number_v1.rb +77 -0
  263. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +61 -0
  264. data/lib/webhookdb/replicator/increase_account_v1.rb +63 -0
  265. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +78 -0
  266. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +64 -0
  267. data/lib/webhookdb/replicator/increase_limit_v1.rb +78 -0
  268. data/lib/webhookdb/replicator/increase_transaction_v1.rb +74 -0
  269. data/lib/webhookdb/replicator/increase_v1_mixin.rb +121 -0
  270. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +61 -0
  271. data/lib/webhookdb/replicator/intercom_contact_v1.rb +36 -0
  272. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +38 -0
  273. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +69 -0
  274. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +105 -0
  275. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +65 -0
  276. data/lib/webhookdb/replicator/plivo_sms_inbound_v1.rb +102 -0
  277. data/lib/webhookdb/replicator/postmark_inbound_message_v1.rb +94 -0
  278. data/lib/webhookdb/replicator/postmark_outbound_message_event_v1.rb +107 -0
  279. data/lib/webhookdb/replicator/schema_modification.rb +42 -0
  280. data/lib/webhookdb/replicator/shopify_customer_v1.rb +58 -0
  281. data/lib/webhookdb/replicator/shopify_order_v1.rb +64 -0
  282. data/lib/webhookdb/replicator/shopify_v1_mixin.rb +161 -0
  283. data/lib/webhookdb/replicator/signalwire_message_v1.rb +169 -0
  284. data/lib/webhookdb/replicator/sponsy_customer_v1.rb +54 -0
  285. data/lib/webhookdb/replicator/sponsy_placement_v1.rb +34 -0
  286. data/lib/webhookdb/replicator/sponsy_publication_v1.rb +125 -0
  287. data/lib/webhookdb/replicator/sponsy_slot_v1.rb +41 -0
  288. data/lib/webhookdb/replicator/sponsy_status_v1.rb +35 -0
  289. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +165 -0
  290. data/lib/webhookdb/replicator/state_machine_step.rb +69 -0
  291. data/lib/webhookdb/replicator/stripe_charge_v1.rb +77 -0
  292. data/lib/webhookdb/replicator/stripe_coupon_v1.rb +62 -0
  293. data/lib/webhookdb/replicator/stripe_customer_v1.rb +60 -0
  294. data/lib/webhookdb/replicator/stripe_dispute_v1.rb +77 -0
  295. data/lib/webhookdb/replicator/stripe_invoice_item_v1.rb +82 -0
  296. data/lib/webhookdb/replicator/stripe_invoice_v1.rb +116 -0
  297. data/lib/webhookdb/replicator/stripe_payout_v1.rb +67 -0
  298. data/lib/webhookdb/replicator/stripe_price_v1.rb +60 -0
  299. data/lib/webhookdb/replicator/stripe_product_v1.rb +60 -0
  300. data/lib/webhookdb/replicator/stripe_refund_v1.rb +101 -0
  301. data/lib/webhookdb/replicator/stripe_subscription_item_v1.rb +56 -0
  302. data/lib/webhookdb/replicator/stripe_subscription_v1.rb +75 -0
  303. data/lib/webhookdb/replicator/stripe_v1_mixin.rb +116 -0
  304. data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +141 -0
  305. data/lib/webhookdb/replicator/transistor_episode_v1.rb +169 -0
  306. data/lib/webhookdb/replicator/transistor_show_v1.rb +68 -0
  307. data/lib/webhookdb/replicator/transistor_v1_mixin.rb +65 -0
  308. data/lib/webhookdb/replicator/twilio_sms_v1.rb +156 -0
  309. data/lib/webhookdb/replicator/webhook_request.rb +5 -0
  310. data/lib/webhookdb/replicator/webhookdb_customer_v1.rb +74 -0
  311. data/lib/webhookdb/replicator.rb +224 -0
  312. data/lib/webhookdb/role.rb +42 -0
  313. data/lib/webhookdb/sentry.rb +35 -0
  314. data/lib/webhookdb/service/auth.rb +138 -0
  315. data/lib/webhookdb/service/collection.rb +91 -0
  316. data/lib/webhookdb/service/entities.rb +97 -0
  317. data/lib/webhookdb/service/helpers.rb +270 -0
  318. data/lib/webhookdb/service/middleware.rb +124 -0
  319. data/lib/webhookdb/service/types.rb +30 -0
  320. data/lib/webhookdb/service/validators.rb +32 -0
  321. data/lib/webhookdb/service/view_api.rb +63 -0
  322. data/lib/webhookdb/service.rb +219 -0
  323. data/lib/webhookdb/service_integration.rb +332 -0
  324. data/lib/webhookdb/shopify.rb +35 -0
  325. data/lib/webhookdb/signalwire.rb +13 -0
  326. data/lib/webhookdb/slack.rb +68 -0
  327. data/lib/webhookdb/snowflake.rb +90 -0
  328. data/lib/webhookdb/spec_helpers/async.rb +122 -0
  329. data/lib/webhookdb/spec_helpers/citest.rb +88 -0
  330. data/lib/webhookdb/spec_helpers/integration.rb +121 -0
  331. data/lib/webhookdb/spec_helpers/message.rb +41 -0
  332. data/lib/webhookdb/spec_helpers/postgres.rb +220 -0
  333. data/lib/webhookdb/spec_helpers/service.rb +432 -0
  334. data/lib/webhookdb/spec_helpers/shared_examples_for_columns.rb +56 -0
  335. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +915 -0
  336. data/lib/webhookdb/spec_helpers/whdb.rb +139 -0
  337. data/lib/webhookdb/spec_helpers.rb +63 -0
  338. data/lib/webhookdb/sponsy.rb +14 -0
  339. data/lib/webhookdb/stripe.rb +37 -0
  340. data/lib/webhookdb/subscription.rb +203 -0
  341. data/lib/webhookdb/sync_target.rb +491 -0
  342. data/lib/webhookdb/tasks/admin.rb +49 -0
  343. data/lib/webhookdb/tasks/annotate.rb +36 -0
  344. data/lib/webhookdb/tasks/db.rb +82 -0
  345. data/lib/webhookdb/tasks/docs.rb +42 -0
  346. data/lib/webhookdb/tasks/fixture.rb +35 -0
  347. data/lib/webhookdb/tasks/message.rb +50 -0
  348. data/lib/webhookdb/tasks/regress.rb +87 -0
  349. data/lib/webhookdb/tasks/release.rb +27 -0
  350. data/lib/webhookdb/tasks/sidekiq.rb +23 -0
  351. data/lib/webhookdb/tasks/specs.rb +64 -0
  352. data/lib/webhookdb/theranest.rb +15 -0
  353. data/lib/webhookdb/transistor.rb +13 -0
  354. data/lib/webhookdb/twilio.rb +13 -0
  355. data/lib/webhookdb/typed_struct.rb +44 -0
  356. data/lib/webhookdb/version.rb +5 -0
  357. data/lib/webhookdb/webhook_response.rb +50 -0
  358. data/lib/webhookdb/webhook_subscription/delivery.rb +82 -0
  359. data/lib/webhookdb/webhook_subscription.rb +226 -0
  360. data/lib/webhookdb/windows_tz.rb +32 -0
  361. data/lib/webhookdb/xml.rb +92 -0
  362. data/lib/webhookdb.rb +224 -0
  363. data/lib/webterm/apps.rb +45 -0
  364. metadata +1129 -0
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grape_entity"
4
+
5
+ require "webhookdb/service/entities"
6
+ require "webhookdb/api" unless defined? Webhookdb::API
7
+
8
+ module Webhookdb::API
9
+ MoneyEntity = Webhookdb::Service::Entities::Money
10
+ TimeRangeEntity = Webhookdb::Service::Entities::TimeRange
11
+
12
+ class BaseEntity < Webhookdb::Service::Entities::Base; end
13
+
14
+ class OrganizationEntity < BaseEntity
15
+ expose :id
16
+ expose :name
17
+ expose :key
18
+ end
19
+
20
+ class OrganizationMembershipEntity < BaseEntity
21
+ expose :customer_email, as: :email
22
+ expose :organization, with: OrganizationEntity
23
+ expose :organization_name, &self.delegate_to(:organization, :name)
24
+ expose :organization_key, &self.delegate_to(:organization, :key)
25
+ expose :status
26
+
27
+ def self.display_headers
28
+ return [
29
+ [:email, "Email"],
30
+ [:organization_name, "Organization Name"],
31
+ [:organization_key, "Organization Key"],
32
+ [:status, "Role"],
33
+ ]
34
+ end
35
+ end
36
+
37
+ class CurrentCustomerEntity < BaseEntity
38
+ expose :email
39
+ expose :name
40
+ expose :default_organization, with: OrganizationEntity
41
+ expose :default_organization_formatted,
42
+ &self.delegate_to(:default_organization, :display_string, safe_with_default: "")
43
+ expose :verified_memberships, with: OrganizationMembershipEntity
44
+ expose :verified_memberships_formatted do |instance|
45
+ lines = instance.verified_memberships.map { |m| "#{m.organization.display_string}: #{m.status}" }
46
+ lines.join("\n")
47
+ end
48
+ expose :invited_memberships, as: :invitations, with: OrganizationMembershipEntity
49
+ expose :invitations_formatted do |instance|
50
+ lines = instance.invited_memberships.map { |m| "#{m.organization.display_string}: #{m.invitation_code}" }
51
+ lines.join("\n")
52
+ end
53
+ expose :display_headers do |_|
54
+ [
55
+ [:email, "Email"],
56
+ [:default_organization_formatted, "Default Org"],
57
+ [:verified_memberships_formatted, "Memberships"],
58
+ [:invitations_formatted, "Invitations"],
59
+ ]
60
+ end
61
+ end
62
+
63
+ class ServiceIntegrationEntity < BaseEntity
64
+ expose :opaque_id
65
+ expose :service_name
66
+ expose :table_name
67
+
68
+ def self.display_headers
69
+ return [[:service_name, "Name"], [:table_name, "Table"], [:opaque_id, "Id"]]
70
+ end
71
+ end
72
+
73
+ class ServiceEntity < BaseEntity
74
+ expose :name
75
+
76
+ def self.display_headers
77
+ return [[:name, "Name"]]
78
+ end
79
+ end
80
+
81
+ class StateMachineEntity < BaseEntity
82
+ expose :needs_input
83
+ expose :prompt
84
+ expose :prompt_is_secret
85
+ expose :post_to_url
86
+ expose :post_params
87
+ expose :post_params_value_key
88
+ expose :complete
89
+ expose :output
90
+ expose :error_code
91
+ expose :extras do |_, opts|
92
+ opts[:extras] || {}
93
+ end
94
+ end
95
+
96
+ class SubscriptionPlanEntity < BaseEntity
97
+ expose :key
98
+ expose :description
99
+ expose :price, with: MoneyEntity
100
+ expose :price_formatted, &self.delegate_to(:price, :format)
101
+
102
+ def self.display_headers
103
+ return [[:key, "Key"], [:description, "Description"], [:price_formatted, "Price"]]
104
+ end
105
+ end
106
+
107
+ class WebhookSubscriptionEntity < BaseEntity
108
+ expose :created_at
109
+ expose :opaque_id
110
+ expose :deliver_to_url
111
+ expose :organization, with: OrganizationEntity
112
+ expose :service_integration, with: ServiceIntegrationEntity
113
+ expose :associated_type
114
+ expose :associated_id
115
+ expose :status
116
+
117
+ def self.display_headers
118
+ return [
119
+ [:opaque_id, "Id"],
120
+ [:deliver_to_url, "Url"],
121
+ [:status, "Status"],
122
+ [:associated_type, "Associated Type"],
123
+ [:associated_id, "Associated Id"],
124
+ ]
125
+ end
126
+ end
127
+
128
+ class BaseSyncTargetEntity < BaseEntity
129
+ expose :created_at
130
+ expose :opaque_id
131
+ expose :service_integration, with: ServiceIntegrationEntity
132
+ expose :period_seconds
133
+ expose :displaysafe_connection_url, as: :connection_url
134
+ expose :table
135
+ expose :schema
136
+ expose :last_synced_at
137
+ expose :associated_type
138
+ expose :associated_id
139
+ expose :associated_object_display
140
+ end
141
+
142
+ class DbSyncTargetEntity < BaseSyncTargetEntity
143
+ expose :schema_and_table_string
144
+
145
+ def self.display_headers
146
+ return [
147
+ [:opaque_id, "Id"],
148
+ [:connection_url, "URL"],
149
+ [:associated_object_display, "Associated"],
150
+ [:schema_and_table_string, "Table"],
151
+ [:last_synced_at, "Last Synced"],
152
+ [:period_seconds, "Period"],
153
+ [:page_size, "Page"],
154
+ ]
155
+ end
156
+ end
157
+
158
+ class HttpSyncTargetEntity < BaseSyncTargetEntity
159
+ def self.display_headers
160
+ return [
161
+ [:opaque_id, "Id"],
162
+ [:connection_url, "URL"],
163
+ [:associated_object_display, "Associated"],
164
+ [:last_synced_at, "Last Synced"],
165
+ [:period_seconds, "Period"],
166
+ [:page_size, "Page"],
167
+ ]
168
+ end
169
+ end
170
+
171
+ class DatabaseMigrationEntity < BaseEntity
172
+ expose :created_at
173
+ expose :started_at
174
+ expose :finished_at
175
+ expose :displaysafe_source_url, as: :source_url
176
+ expose :displaysafe_destination_url, as: :destination_url
177
+ expose :status
178
+
179
+ def self.display_headers
180
+ return [
181
+ [:created_at, "Created at"],
182
+ [:started_at, "Started at"],
183
+ [:finished_at, "Finished at"],
184
+ [:source_url, "Source"],
185
+ [:destination_url, "Destination"],
186
+ [:status, "Status"],
187
+ ]
188
+ end
189
+ end
190
+
191
+ class BackfillJobEntity < BaseEntity
192
+ expose :opaque_id, as: :id
193
+ expose :status
194
+ expose :started_at
195
+ expose :fully_finished_at, as: :finished_at
196
+ expose :service_integration, with: ServiceIntegrationEntity
197
+ end
198
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grape"
4
+ require "webhookdb/demo_mode"
5
+ require "webhookdb/jobs/process_webhook"
6
+
7
+ module Webhookdb::API::Helpers
8
+ extend Grape::API::Helpers
9
+
10
+ # Prompt for input given some criteria.
11
+ #
12
+ # NOTE: You cannot use :prompt with 'requires', or 'allow_blank'.
13
+ # You MUST use 'optional', and not specify allow_blank
14
+ # (validation will error if these conditions are not met).
15
+ # The semantics around prompts are too subtle to use these built-in validators;
16
+ # we must use our own.
17
+ # Note also, while :default will not error (because we cannot detect it easily),
18
+ # it should not be used since it defeats the purpose of :prompt.
19
+ #
20
+ # The main complexity is around optional params
21
+ # that will take a given default if blank (but we need to prompt if not supplied),
22
+ # or 'enter to confirm' type prompts, where we want to prompt if not supplied.
23
+ #
24
+ # To handle these cases, use the :optional and :confirm options, as below.
25
+ # On the Go client, use string pointers for these values, with omitempty;
26
+ # they will still submit an empty string, but will not include the key if nil.
27
+ #
28
+ # Examples:
29
+ #
30
+ # requires :param, prompt: "Enter a value:"
31
+ #
32
+ # This is by far the most common usage.
33
+ # If :param is not present (ie, `params[:param].present?` is falsy),
34
+ # a 422 is returned with a StateMachineStep with a prompt value of 'Enter a value:'.
35
+ #
36
+ # All other forms use a Hash instead of a string as the argument.
37
+ #
38
+ # requires :param, prompt: {message: 'Enter secret:', secret: true}
39
+ #
40
+ # Same as above, but the prompt is set to be a secret.
41
+ #
42
+ # requires :param, prompt: {message: 'Press Enter to confirm, or Ctrl+C to cancel:', confirm: true}
43
+ #
44
+ # This will 422 if :param is not provided or is nil;
45
+ # it will pass otherwise (so empty values, like '' or false, are valid).
46
+ # This is used to guard against actions that need confirmation.
47
+ # Note that in many situations, we don't know about the need to confirm
48
+ # until we're into the body of the endpoint. In these cases,
49
+ # use `Webhookdb::API::Helpers.prompt_for_required_param!` directly,
50
+ # along with something like `optional :param`.
51
+ #
52
+ # requires :param, prompt: {message: 'This will default', optional: true}
53
+ #
54
+ # This will 422 if not provided, but will pass otherwise
55
+ # (so nil, '', and false are all valid values).
56
+ #
57
+ # optional :param, prompt: {message: 'Bypassable', disable: ->(req) { req.env['HTTP_NOPROMPT']} }
58
+ #
59
+ # This will disable the prompt behavior if the proc given to :disable returns true.
60
+ # This is mostly useful when we want to avoid prompting for something
61
+ # because the endpoint is going to have some particular behavior that will avoid the purpose of the prompt.
62
+ #
63
+ class Prompt < ::Grape::Validations::Validators::Base
64
+ def validate(request)
65
+ raise "allow_blank must not be set" unless @allow_blank.nil?
66
+ attr_name = @attrs.first
67
+ if @option.is_a?(String)
68
+ options = {message: @option}
69
+ else
70
+ options = @option
71
+ raise "Missing :message key in prompt args" unless options[:message]
72
+ end
73
+ raise "must use optional for #{attr_name}" if @required
74
+ return unless self.needs_prompt?(attr_name, request, options)
75
+ Webhookdb::API::Helpers.prompt_for_required_param!(
76
+ request,
77
+ attr_name,
78
+ options[:message],
79
+ secret: options[:secret] || false,
80
+ )
81
+ end
82
+
83
+ protected def needs_prompt?(attr_name, request, options)
84
+ if (disable_proc = options[:disable]) && (disable_proc[request])
85
+ return false
86
+ end
87
+ options[:demo_mode_proc][request] if Webhookdb::DemoMode.client_enabled? && options[:demo_mode_proc]
88
+ params = request.params
89
+ if options[:confirm]
90
+ return true unless params.key?(attr_name)
91
+ return true if params[attr_name].nil?
92
+ return false
93
+ end
94
+ if options[:optional]
95
+ return true unless params.key?(attr_name)
96
+ return false
97
+ end
98
+ value = params[attr_name]
99
+ return false if value.present?
100
+ return false if value.is_a?(FalseClass)
101
+ return true
102
+ end
103
+ end
104
+
105
+ def self.prompt_for_required_param!(request, key, prompt, secret: false, output: "")
106
+ step = Webhookdb::Replicator::StateMachineStep.new
107
+ step.output = output
108
+ step.post_to_url = request.path
109
+ step.post_params = request.params.to_h
110
+ step.post_params_value_key = key
111
+ step.set_prompt(prompt, secret:)
112
+ body = Webhookdb::Service.error_body(
113
+ 422,
114
+ "Prompt for required params",
115
+ code: "prompt_required_params",
116
+ more: {state_machine_step: Webhookdb::API::StateMachineEntity.represent(step)},
117
+ )
118
+ throw :error, message: body, status: 422, headers: {"Whdb-Prompt" => key.to_s}
119
+ end
120
+
121
+ def lookup_service_integration!(org, identifier)
122
+ sints = org.service_integrations_dataset.
123
+ where(Sequel[service_name: identifier] | Sequel[table_name: identifier] | Sequel[opaque_id: identifier]).
124
+ limit(2).all
125
+ return sints.first if sints.size == 1
126
+ merror!(403, "There is no service integration with that identifier.") if sints.empty?
127
+ dupe_attr = nil
128
+ alternative = nil
129
+ if sints.first.service_name == identifier
130
+ dupe_attr = "service name"
131
+ alternative = "table name"
132
+ else
133
+ dupe_attr = "table name"
134
+ alternative = "service name"
135
+ end
136
+ msg403 = "There are multiple integrations with that #{dupe_attr}. " \
137
+ "Try again using an integration id, or a #{alternative}. " \
138
+ "Use `webhookdb integrations list` to see all integrations."
139
+ merror!(409, msg403)
140
+ end
141
+
142
+ # Our primary webhook endpoint is /v1/service_integrations/<opaque_id>,
143
+ # but in some cases we need a 'static' endpoint for apps to send to,
144
+ # like /v1/install/front/webhooks.
145
+ # Those endpoints share the webhook handling behavior with this method.
146
+ #
147
+ # The block passed to this method yields the service integration.
148
+ # This is important because we want to make sure to log the webhook if something goes wrong
149
+ # while looking up the service integration.
150
+ #
151
+ # The potential_opaque_id should be a way to identify who is responsible for
152
+ # the webhook request. For example, to `/v1/service_integrations/svi_abc`,
153
+ # this would be `svi_abc` (even though it's an invalid opaque id).
154
+ # In other cases, especially marketplace integrations, this could be some other value
155
+ # to identify the webhook that was sent.
156
+ #
157
+ # If the block yields the Symbol :pass, no further handling is done;
158
+ # this would be done for example when there is no valid service integration.
159
+ # Otherwise, the block must yield a service integration.
160
+ def handle_webhook_request(potential_opaque_id, &)
161
+ opaque_id = potential_opaque_id
162
+ organization_id = nil
163
+ s_status = nil
164
+ request_headers = {}
165
+ raise LocalJumpError unless block_given?
166
+ begin
167
+ sint = yield
168
+ return if sint == :pass
169
+ raise "error instead of return nil if there is no service integration" if sint.nil?
170
+ opaque_id = sint.opaque_id
171
+ organization_id = sint.organization_id
172
+ request_headers = request.headers.dup
173
+ if (content_type = env["CONTENT_TYPE"])
174
+ request_headers["Content-Type"] = content_type
175
+ end
176
+ svc = Webhookdb::Replicator.create(sint).dispatch_request_to(request)
177
+ svc.preprocess_headers_for_logging(request_headers)
178
+ handling_sint = svc.service_integration
179
+ whresp = svc.webhook_response(request)
180
+ s_status, s_headers, s_body = whresp.to_rack
181
+ (s_status = 200) if s_status >= 400 && Webhookdb.regression_mode?
182
+
183
+ if s_status >= 400
184
+ logger.warn "rejected_webhook", webhook_headers: request_headers, webhook_body: env["api.request.body"]
185
+ header "Whdb-Rejected-Reason", whresp.reason
186
+ else
187
+ req_body = env.key?("api.request.body") ? env["api.request.body"] : env["rack.input"].read
188
+ req_body = {} if req_body.blank?
189
+ process_kwargs = {
190
+ headers: request_headers,
191
+ body: req_body,
192
+ request_path: request.path_info,
193
+ request_method: request.request_method,
194
+ }
195
+ event_json = Amigo::Event.create(
196
+ "webhookdb.serviceintegration.webhook", [handling_sint.id, process_kwargs],
197
+ ).as_json
198
+ # Audit Log this synchronously.
199
+ # It should be fast enough. We may as well log here so we can avoid
200
+ # serializing the (large) webhook payload multiple times, as with normal pubsub.
201
+ Webhookdb::Async::AuditLogger.new.perform(event_json)
202
+ if svc.process_webhooks_synchronously? || Webhookdb::Replicator.always_process_synchronously
203
+ whreq = Webhookdb::Replicator::WebhookRequest.new(
204
+ method: process_kwargs[:request_method],
205
+ path: process_kwargs[:request_path],
206
+ headers: process_kwargs[:headers],
207
+ body: process_kwargs[:body],
208
+ )
209
+ inserted = svc.upsert_webhook(whreq)
210
+ s_body = svc.synchronous_processing_response_body(upserted: inserted, request: whreq)
211
+ else
212
+ queue = svc.upsert_has_deps? ? "netout" : "webhook"
213
+ Webhookdb::Jobs::ProcessWebhook.set(queue:).perform_async(event_json)
214
+ end
215
+ end
216
+
217
+ s_headers.each { |k, v| header k, v }
218
+ if s_headers["Content-Type"] == "application/json"
219
+ body Oj.load(s_body)
220
+ else
221
+ env["api.format"] = :binary
222
+ body s_body
223
+ end
224
+ status s_status
225
+ ensure
226
+ _log_webhook_request(opaque_id, organization_id, s_status, request_headers)
227
+ end
228
+ end
229
+
230
+ def _log_webhook_request(opaque_id, organization_id, sstatus, request_headers)
231
+ return if request.headers[Webhookdb::LoggedWebhook::RETRY_HEADER]
232
+ # Status can be set from:
233
+ # - the 'status' method, which will be 201 if it hasn't been set,
234
+ # or another value if it has been set.
235
+ # - the webhook responder, which could respond with 401, etc
236
+ # - if there was an exception- so no status is set yet- use 0
237
+ # The main thing to watch out for is that we:
238
+ # - Cannot assume an exception is a 500 (it can be rescued later)
239
+ # - Must handle error! calls
240
+ # Anyway, this is all pretty confusing, but it's all tested.
241
+ rstatus = status == 201 ? (sstatus || 0) : status
242
+ request.body.rewind
243
+ Webhookdb::LoggedWebhook.resilient_insert(
244
+ request_body: request.body.read,
245
+ request_headers: request_headers.to_json,
246
+ request_method: request.request_method,
247
+ request_path: request.path_info,
248
+ response_status: rstatus,
249
+ organization_id:,
250
+ service_integration_opaque_id: opaque_id,
251
+ )
252
+ end
253
+ end