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,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