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,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/plivo"
4
+
5
+ class Webhookdb::Replicator::PlivoSmsInboundV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+
8
+ # @return [Webhookdb::Replicator::Descriptor]
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "plivo_sms_inbound_v1",
12
+ ctor: ->(sint) { Webhookdb::Replicator::PlivoSmsInboundV1.new(sint) },
13
+ feature_roles: ["beta"],
14
+ resource_name_singular: "Plivo Inbound SMS Message",
15
+ supports_webhooks: true,
16
+ api_docs_url: "https://www.plivo.com/docs/sms/api/message#the-message-object",
17
+ )
18
+ end
19
+
20
+ def _remote_key_column
21
+ return Webhookdb::Replicator::Column.new(:plivo_message_uuid, TEXT, data_key: "MessageUUID")
22
+ end
23
+
24
+ def _denormalized_columns
25
+ return [
26
+ Webhookdb::Replicator::Column.new(:row_inserted_at, TIMESTAMP, defaulter: :now, optional: true, index: true),
27
+ Webhookdb::Replicator::Column.new(:from_number, TEXT, data_key: "From", index: true),
28
+ Webhookdb::Replicator::Column.new(:to_number, TEXT, data_key: "To", index: true),
29
+ ]
30
+ end
31
+
32
+ def _timestamp_column_name
33
+ return :row_inserted_at
34
+ end
35
+
36
+ def _update_where_expr
37
+ # These are immutable events, not updates, so never update after inserted.
38
+ return Sequel[false]
39
+ end
40
+
41
+ def _webhook_response(request)
42
+ return Webhookdb::Plivo.webhook_response(request, self.service_integration.backfill_secret)
43
+ end
44
+
45
+ def _resource_and_event(request)
46
+ body = request.body
47
+ raise Webhookdb::InvalidPrecondition, "body should be form-encoded string" unless body.is_a?(String)
48
+ resource = URI.decode_www_form(body).to_h
49
+ return resource, nil
50
+ end
51
+
52
+ INTEGER_KEYS = ["TotalAmount", "TotalRate", "Units"].freeze
53
+
54
+ def _resource_to_data(resource, *)
55
+ super
56
+ h = resource.dup
57
+ INTEGER_KEYS.each do |k|
58
+ h[k] = h[k].to_i if h.key?(k)
59
+ end
60
+ return h
61
+ end
62
+
63
+ def calculate_webhook_state_machine
64
+ step = Webhookdb::Replicator::StateMachineStep.new
65
+ if self.service_integration.backfill_key.blank?
66
+ step.output = %(You are about to set up an endpoint to receive #{self.resource_name_plural}.
67
+ You'll need to give us your Auth ID and Auth Token so we can validate webhooks.
68
+ Once that is set up, we'll help you set up your WebhookDB endpoint in Plivo.)
69
+ return step.secret_prompt("Auth ID").backfill_key(self.service_integration)
70
+ end
71
+ if self.service_integration.backfill_secret.blank?
72
+ return step.secret_prompt("Auth Token").backfill_secret(self.service_integration)
73
+ end
74
+ begin
75
+ Webhookdb::Plivo.request(
76
+ :get,
77
+ "/",
78
+ auth_id: self.service_integration.backfill_key,
79
+ auth_token: self.service_integration.backfill_secret,
80
+ timeout: Webhookdb::Plivo.http_timeout,
81
+ )
82
+ rescue Webhookdb::Http::Error => e
83
+ self.service_integration.update(backfill_key: "", backfill_secret: "")
84
+ step.output = %(Those credentials didn't work (Plivo returned an HTTP #{e.status} error).
85
+ Let's start over with your Auth ID (it probably begins with an MA or SA).)
86
+ return step.secret_prompt("Auth ID").backfill_key(self.service_integration)
87
+ end
88
+ step.output = %(Perfect, those credentials check out.
89
+ You can use this endpoint in your Plivo Application to receive webhooks:
90
+
91
+ #{self._webhook_endpoint}
92
+
93
+ This can be done through the UI, or the API by creating or updating an Application under your Account.
94
+
95
+ As messages comes in, they'll be upserted into your table.
96
+ #{self._query_help_output}
97
+
98
+ You can also use `webhookdb httpsync` to set up notifications to your own server
99
+ when rows are modified.)
100
+ return step.completed
101
+ end
102
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/postmark"
4
+
5
+ class Webhookdb::Replicator::PostmarkInboundMessageV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+
8
+ # @return [Webhookdb::Replicator::Descriptor]
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "postmark_inbound_message_v1",
12
+ ctor: ->(sint) { Webhookdb::Replicator::PostmarkInboundMessageV1.new(sint) },
13
+ feature_roles: [],
14
+ resource_name_singular: "Postmark Inbound Message",
15
+ supports_webhooks: true,
16
+ api_docs_url: "https://postmarkapp.com/developer/user-guide/inbound/parse-an-email",
17
+ )
18
+ end
19
+
20
+ def _remote_key_column
21
+ return Webhookdb::Replicator::Column.new(:message_id, TEXT, data_key: "MessageID")
22
+ end
23
+
24
+ def _denormalized_columns
25
+ col = Webhookdb::Replicator::Column
26
+ return [
27
+ col.new(:from_email, TEXT, index: true, data_key: ["FromFull", "Email"]),
28
+ col.new(:to_email, TEXT, index: true, data_key: ["ToFull", 0, "Email"]),
29
+ col.new(:subject, TEXT, index: true, data_key: "Subject"),
30
+ col.new(:timestamp, TIMESTAMP, index: true, data_key: "Date"),
31
+ col.new(:tag, TEXT, index: true, data_key: "Tag"),
32
+ ]
33
+ end
34
+
35
+ def _prepare_for_insert(*)
36
+ h = super
37
+ ts_str = h[:timestamp]
38
+ # We get some weird time formats, like 'Wed, 05 Jul 2023 22:27:31 +0000 (UTC)'.
39
+ # Ruby can parse these, but PG cannot, so sanitize the ' (UTC)' out of here.
40
+ # Depending on what other random stuff we see, we can make this more general later.
41
+ h[:timestamp] = ts_str.gsub(/ \(UTC\)$/, "")
42
+ return h
43
+ end
44
+
45
+ def _timestamp_column_name
46
+ return :timestamp
47
+ end
48
+
49
+ def _resource_and_event(request)
50
+ return request.body, nil
51
+ end
52
+
53
+ def _update_where_expr
54
+ # These are immutable events, not updates, so never update after inserted.
55
+ return Sequel[false]
56
+ end
57
+
58
+ def _webhook_response(request)
59
+ return Webhookdb::Postmark.webhook_response(request)
60
+ end
61
+
62
+ def calculate_webhook_state_machine
63
+ step = Webhookdb::Replicator::StateMachineStep.new
64
+ if self.service_integration.webhook_secret.blank?
65
+ step.output = %(You are about to set up webhooks for Postmark Inbound Messages.
66
+ When emails are sent to the email address configured in Postmark,
67
+ they will show up in WebhookDB automatically.
68
+
69
+ 1. Go to https://account.postmarkapp.com/servers
70
+ 2. Choose the server you want to replicator.
71
+ 3. Choose the Inbound Stream to replicate.
72
+ 2. Go to the 'Settings' tab.
73
+ 3. Use this Webhook URL: #{self.webhook_endpoint}
74
+ 5. Hit 'Check' and verify it works. If it does not, double check your settings.
75
+ 6. Hit 'Save Webhook'.)
76
+ step.set_prompt("Press Enter after Save Webhook succeeds:")
77
+ step.transition_field(self.service_integration, "noop_create")
78
+ self.service_integration.update(webhook_secret: "placeholder")
79
+ return step
80
+ end
81
+ step.output = %(
82
+ All set! Inbound Messages will be synced as they come in.
83
+
84
+ #{self._query_help_output})
85
+ return step.completed
86
+ end
87
+
88
+ def backfill_not_supported_message
89
+ return %(We don't yet support backfilling Postmark Inbound Messages.
90
+ File an issue at #{Webhookdb.oss_repo_url} or email hello@webhookdb.com to let us know if this is something you want!
91
+
92
+ Run `webhookdb integration reset #{self.service_integration.opaque_id}` to go through webhook setup.)
93
+ end
94
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/postmark"
4
+
5
+ class Webhookdb::Replicator::PostmarkOutboundMessageEventV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+
8
+ # @return [Webhookdb::Replicator::Descriptor]
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "postmark_outbound_message_event_v1",
12
+ ctor: ->(sint) { Webhookdb::Replicator::PostmarkOutboundMessageEventV1.new(sint) },
13
+ feature_roles: [],
14
+ resource_name_singular: "Postmark Outbound Message Event",
15
+ supports_webhooks: true,
16
+ api_docs_url: "https://postmarkapp.com/developer/webhooks/webhooks-overview",
17
+ )
18
+ end
19
+
20
+ TIMESTAMP_KEYS = ["ReceivedAt", "DeliveredAt", "BouncedAt", "ChangedAt"].freeze
21
+
22
+ LOOKUP_TIMESTAMP = Webhookdb::Replicator::Column::IsomorphicProc.new(
23
+ ruby: lambda do |resource:, **|
24
+ tskey = TIMESTAMP_KEYS.find { |k| resource[k] }
25
+ raise KeyError, "Cannot find valid timestamp key in #{resource}" if tskey.nil?
26
+ resource[tskey]
27
+ end,
28
+ )
29
+
30
+ BUILD_EVENT_MD5 = Webhookdb::Replicator::Column::IsomorphicProc.new(
31
+ ruby: lambda do |resource:, **|
32
+ md5 = Digest::MD5.new
33
+ md5.update(resource.fetch("MessageID"))
34
+ md5.update(resource.fetch("RecordType"))
35
+ md5.update(LOOKUP_TIMESTAMP.ruby.call(resource:))
36
+ md5.hexdigest
37
+ end,
38
+ )
39
+
40
+ def _remote_key_column
41
+ return Webhookdb::Replicator::Column.new(:event_id, UUID, optional: true, defaulter: BUILD_EVENT_MD5)
42
+ end
43
+
44
+ def _denormalized_columns
45
+ col = Webhookdb::Replicator::Column
46
+ return [
47
+ col.new(:message_id, TEXT, index: true, data_key: "MessageID"),
48
+ col.new(:timestamp, TIMESTAMP, index: true, optional: true, defaulter: LOOKUP_TIMESTAMP),
49
+ col.new(:record_type, TEXT, index: true, optional: true, data_key: "RecordType"),
50
+ col.new(:tag, TEXT, index: true, optional: true, data_key: "Tag"),
51
+ col.new(:recipient, TEXT, index: true, optional: true, data_key: "Recipient"),
52
+ col.new(:changed_at, TIMESTAMP, index: true, optional: true, data_key: "ChangedAt"),
53
+ col.new(:delivered_at, TIMESTAMP, index: true, optional: true, data_key: "DeliveredAt"),
54
+ col.new(:received_at, TIMESTAMP, index: true, optional: true, data_key: "ReceivedAt"),
55
+ col.new(:bounced_at, TIMESTAMP, index: true, optional: true, data_key: "BouncedAt"),
56
+ ]
57
+ end
58
+
59
+ def _timestamp_column_name
60
+ return :timestamp
61
+ end
62
+
63
+ def _resource_and_event(request)
64
+ return request.body, nil
65
+ end
66
+
67
+ def _update_where_expr
68
+ # Since the primary key is based on the timestamp, we never do updates
69
+ return Sequel[false]
70
+ end
71
+
72
+ def _webhook_response(request)
73
+ return Webhookdb::Postmark.webhook_response(request)
74
+ end
75
+
76
+ def calculate_webhook_state_machine
77
+ step = Webhookdb::Replicator::StateMachineStep.new
78
+ if self.service_integration.webhook_secret.blank?
79
+ step.output = %(You are about to set up webhooks for Postmark Outbound Message Events,
80
+ like deliveries, clicks, and opens.
81
+
82
+ 1. In the Postmark UI, locate the server and stream (Transactional or Broadcast) you want to record.
83
+ 2. Click on the 'Webhooks' tab.
84
+ 3. Use this Webhook URL: #{self.webhook_endpoint}
85
+ 4. Check the events you want to send.
86
+ 5. Hit 'Send test' and verify it works. If it does not, double check your settings.
87
+ 6. Hit 'Save Changes')
88
+ step.set_prompt("Press Enter after Saved Changes succeeds:")
89
+ step.transition_field(self.service_integration, "noop_create")
90
+ self.service_integration.update(webhook_secret: "placeholder")
91
+ return step
92
+ end
93
+ step.output = %(
94
+ All set! Events will be synced as they come in.
95
+
96
+ #{self._query_help_output})
97
+ return step.completed
98
+ end
99
+
100
+ def backfill_not_supported_message
101
+ return %(We don't yet support backfilling Postmark Outbound Message Events.
102
+
103
+ File an issue at #{Webhookdb.oss_repo_url} or email hello@webhookdb.com to let us know if this is something you want!
104
+
105
+ Run `webhookdb integration reset #{self.service_integration.opaque_id}` to go through webhook setup.)
106
+ end
107
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Replicator::SchemaModification
4
+ # All of these statements can be sent to the server at once.
5
+ # @return [Array<String>]
6
+ attr_reader :transaction_statements
7
+ # Each of these statements must be executed one-at-a-time.
8
+ # An example would be creating indices concurrently in PG.
9
+ # @return [Array<String>]
10
+ attr_reader :nontransaction_statements
11
+ # Each of these statements are executed in the application database,
12
+ # NOT whatever database the schema modification itself is executed against.
13
+ # @return [Array<String>]
14
+ attr_reader :application_database_statements
15
+
16
+ def initialize
17
+ @transaction_statements = []
18
+ @nontransaction_statements = []
19
+ @application_database_statements = []
20
+ end
21
+
22
+ def noop?
23
+ return @transaction_statements.empty? &&
24
+ @nontransaction_statements.empty? &&
25
+ @application_database_statements.empty?
26
+ end
27
+
28
+ def execute(db)
29
+ db << stmt2str(@transaction_statements)
30
+ @nontransaction_statements.each { |stmt| db << stmt }
31
+ Webhookdb::Postgres::Model.db << stmt2str(@application_database_statements)
32
+ end
33
+
34
+ private def stmt2str(lines)
35
+ return "" if lines.empty?
36
+ return lines.join(";\n") + ";"
37
+ end
38
+
39
+ def to_s
40
+ return [stmt2str(@transaction_statements), stmt2str(@nontransaction_statements)].reject(&:blank?).join("\n")
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/shopify"
4
+ require "webhookdb/replicator/shopify_v1_mixin"
5
+
6
+ class Webhookdb::Replicator::ShopifyCustomerV1 < Webhookdb::Replicator::Base
7
+ include Appydays::Loggable
8
+ include Webhookdb::Replicator::ShopifyV1Mixin
9
+
10
+ # @return [Webhookdb::Replicator::Descriptor]
11
+ def self.descriptor
12
+ return Webhookdb::Replicator::Descriptor.new(
13
+ name: "shopify_customer_v1",
14
+ ctor: ->(sint) { Webhookdb::Replicator::ShopifyCustomerV1.new(sint) },
15
+ feature_roles: [],
16
+ resource_name_singular: "Shopify Customer",
17
+ supports_webhooks: true,
18
+ supports_backfill: true,
19
+ api_docs_url: "https://shopify.dev/docs/api/admin-rest/2023-10/resources/customer",
20
+ )
21
+ end
22
+
23
+ def _remote_key_column
24
+ return Webhookdb::Replicator::Column.new(:shopify_id, TEXT, data_key: "id")
25
+ end
26
+
27
+ def _denormalized_columns
28
+ return [
29
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
30
+ Webhookdb::Replicator::Column.new(:email, TEXT, index: true),
31
+ Webhookdb::Replicator::Column.new(:first_name, TEXT),
32
+ Webhookdb::Replicator::Column.new(:last_name, TEXT),
33
+ Webhookdb::Replicator::Column.new(:last_order_id, TEXT),
34
+ Webhookdb::Replicator::Column.new(:last_order_name, TEXT),
35
+ Webhookdb::Replicator::Column.new(:phone, TEXT, index: true),
36
+ Webhookdb::Replicator::Column.new(:state, TEXT),
37
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
38
+ ]
39
+ end
40
+
41
+ def _update_where_expr
42
+ return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
43
+ end
44
+
45
+ def _mixin_backfill_url
46
+ return "/admin/api/2021-04/customers.json"
47
+ end
48
+
49
+ def _mixin_backfill_hashkey
50
+ return "customers"
51
+ end
52
+
53
+ def _mixin_backfill_warning
54
+ return %(Shopify allows us to backfill your entire Customer history,
55
+ so you're in good shape.
56
+ )
57
+ end
58
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/shopify"
4
+ require "webhookdb/replicator/shopify_v1_mixin"
5
+
6
+ class Webhookdb::Replicator::ShopifyOrderV1 < Webhookdb::Replicator::Base
7
+ include Appydays::Loggable
8
+ include Webhookdb::Replicator::ShopifyV1Mixin
9
+
10
+ # @return [Webhookdb::Replicator::Descriptor]
11
+ def self.descriptor
12
+ return Webhookdb::Replicator::Descriptor.new(
13
+ name: "shopify_order_v1",
14
+ ctor: ->(sint) { Webhookdb::Replicator::ShopifyOrderV1.new(sint) },
15
+ feature_roles: [],
16
+ resource_name_singular: "Shopify Order",
17
+ supports_webhooks: true,
18
+ supports_backfill: true,
19
+ api_docs_url: "https://shopify.dev/docs/api/admin-rest/2023-10/resources/order",
20
+ )
21
+ end
22
+
23
+ def _remote_key_column
24
+ return Webhookdb::Replicator::Column.new(:shopify_id, TEXT, data_key: "id")
25
+ end
26
+
27
+ def _denormalized_columns
28
+ return [
29
+ Webhookdb::Replicator::Column.new(:app_id, TEXT),
30
+ Webhookdb::Replicator::Column.new(:cancelled_at, TIMESTAMP, index: true),
31
+ Webhookdb::Replicator::Column.new(:cart_token, TEXT),
32
+ Webhookdb::Replicator::Column.new(:checkout_token, TEXT),
33
+ Webhookdb::Replicator::Column.new(:closed_at, TIMESTAMP, index: true),
34
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
35
+ Webhookdb::Replicator::Column.new(:customer_id, TEXT,
36
+ index: true, data_key: ["customer", "id"], optional: true,),
37
+ Webhookdb::Replicator::Column.new(:email, TEXT, index: true),
38
+ Webhookdb::Replicator::Column.new(:name, TEXT),
39
+ Webhookdb::Replicator::Column.new(:order_number, INTEGER, index: true),
40
+ Webhookdb::Replicator::Column.new(:phone, TEXT, index: true),
41
+ Webhookdb::Replicator::Column.new(:token, TEXT),
42
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
43
+ Webhookdb::Replicator::Column.new(:user_id, TEXT, index: true),
44
+ ]
45
+ end
46
+
47
+ def _update_where_expr
48
+ return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
49
+ end
50
+
51
+ def _mixin_backfill_url
52
+ return "/admin/api/2021-04/orders.json?status=any"
53
+ end
54
+
55
+ def _mixin_backfill_hashkey
56
+ return "orders"
57
+ end
58
+
59
+ def _mixin_backfill_warning
60
+ return %(Note that Shopify only allows access to orders made in the last 60 days,
61
+ so this history will not be comprehensive.
62
+ Please email #{Webhookdb.support_email} if you need a complete history backfill.)
63
+ end
64
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/shopify"
4
+
5
+ module Webhookdb::Replicator::ShopifyV1Mixin
6
+ def _mixin_backfill_url
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def _mixin_backfill_hashkey
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def _mixin_backfill_warning
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def _timestamp_column_name
19
+ return :updated_at
20
+ end
21
+
22
+ # For Shopify endpoints the object and webhook have the same shape—the webhook is simply the updated object
23
+ def _resource_and_event(request)
24
+ return request.body, nil
25
+ end
26
+
27
+ def _webhook_response(request)
28
+ # info for debugging
29
+ shopify_auth = request.env["HTTP_X_SHOPIFY_HMAC_SHA256"]
30
+ log_params = {shopify_auth:, shopify_body: request.params}
31
+ self.logger.debug "webhook hit shopify endpoint", log_params
32
+
33
+ return Webhookdb::WebhookResponse.error("missing hmac") if shopify_auth.nil?
34
+ request.body.rewind
35
+ request_data = request.body.read
36
+ verified = Webhookdb::Shopify.verify_webhook(request_data, shopify_auth, self.service_integration.webhook_secret)
37
+ return Webhookdb::WebhookResponse.ok if verified
38
+ return Webhookdb::WebhookResponse.error("invalid hmac")
39
+ end
40
+
41
+ def process_state_change(field, value)
42
+ # special handling for converting a shop name into an api url
43
+ if field == "shop_name"
44
+ # revisionist history
45
+ field = "api_url"
46
+ value = "https://#{value}.myshopify.com"
47
+ end
48
+ return super(field, value)
49
+ end
50
+
51
+ def calculate_webhook_state_machine
52
+ step = Webhookdb::Replicator::StateMachineStep.new
53
+ # if the service integration doesn't exist, create it with some standard values
54
+ unless self.service_integration.webhook_secret.present?
55
+ step.needs_input = true
56
+ step.output = %(You are about to start replicating #{self.resource_name_plural} into WebhookDB.
57
+ We've made an endpoint available for #{self.resource_name_singular} webhooks:
58
+
59
+ #{self._webhook_endpoint}
60
+
61
+ From your Shopify admin dashboard, go to Settings -> Notifications.
62
+ Scroll down to the Webhook Section.
63
+ You will need to create a separate webhook for each #{self.resource_name_singular} event,
64
+ but you can use the URL above and select JSON as the desired format for all of them.
65
+
66
+ At the very bottom of the page, you should see a signing secret that will be used to verify all webhooks.
67
+ Copy that value.)
68
+ return step.secret_prompt("secret").webhook_secret(self.service_integration)
69
+ end
70
+
71
+ step.output = %(Great! WebhookDB is now listening for #{self.resource_name_singular} webhooks.
72
+ #{self._query_help_output}
73
+ In order to backfill existing #{self.resource_name_plural}, run this from a shell:
74
+
75
+ #{self._backfill_command}
76
+ )
77
+ return step.completed
78
+ end
79
+
80
+ def calculate_backfill_state_machine
81
+ step = Webhookdb::Replicator::StateMachineStep.new
82
+ unless self.service_integration.backfill_key.present?
83
+ step.output = \
84
+ %(In order to backfill #{self.resource_name_plural}, we need an API key and password
85
+ (file an issue at #{Webhookdb.oss_repo_url} if you need token support).
86
+
87
+ - From your Shopify Dashboard, go to Apps and click the "Manage Private Apps" link at the bottom of the page.
88
+ - Then click "Create Private App" and fill out the necessary information.
89
+ - When you get to the "Admin API" section,
90
+ select "Read Access" for the #{self.resource_name_singular} API and leave the rest as is.
91
+ - Then hit "Save" and create the app.
92
+ - You'll be presented with a page that has info about your app's credentials.
93
+
94
+ We need both the API Key and Password.)
95
+ return step.secret_prompt("API Key").backfill_key(self.service_integration)
96
+ end
97
+
98
+ unless self.service_integration.backfill_secret.present?
99
+ return step.secret_prompt("Password").backfill_secret(self.service_integration)
100
+ end
101
+
102
+ unless self.service_integration.api_url.present?
103
+ step.output = %(Nice! Now we need the name of your shop so that we can construct the api url.
104
+ This is the name that is used by Shopify for URL purposes.
105
+ It should be in the top left corner of your Admin Dashboard next to the Shopify logo.)
106
+ step.post_to_url = self.service_integration.authed_api_path + "/transition/shop_name"
107
+ return step.prompting("Shop Name")
108
+ end
109
+
110
+ # we check backfill credentials *after* entering the api_url because it is required to establish the auth connection
111
+ unless (result = self.verify_backfill_credentials).verified
112
+ self.service_integration.replicator.clear_backfill_information
113
+ step.output = result.message
114
+ return step.secret_prompt("API Key").backfill_key(self.service_integration)
115
+ end
116
+
117
+ step.output = %(Great! We are going to start backfilling your #{self.resource_name_plural}.
118
+ #{self._mixin_backfill_warning}
119
+ #{self._query_help_output})
120
+ return step.completed
121
+ end
122
+
123
+ def _verify_backfill_403_err_msg
124
+ return "It looks like that API Key does not have permission to access #{self.resource_name_singular} Records. " \
125
+ "Please check the permissions by going to your private app page and " \
126
+ "looking at the list of active permissions. " \
127
+ "Once you've verified or corrected the permissions for this key, " \
128
+ "please reenter the API Key you just created:"
129
+ end
130
+
131
+ def _verify_backfill_401_err_msg
132
+ return "It looks like that API Key/Access Token combination is invalid. " \
133
+ "Please reenter the API Key you just created:"
134
+ end
135
+
136
+ def _verify_backfill_err_msg
137
+ return "An error occurred. Please reenter the API Key you just created:"
138
+ end
139
+
140
+ def _fetch_backfill_page(pagination_token, **_kwargs)
141
+ url = if pagination_token.blank?
142
+ self.service_integration.api_url + self._mixin_backfill_url
143
+ else
144
+ pagination_token
145
+ end
146
+ response = Webhookdb::Http.get(
147
+ url,
148
+ basic_auth: {username: self.service_integration.backfill_key,
149
+ password: self.service_integration.backfill_secret,},
150
+ logger: self.logger,
151
+ timeout: Webhookdb::Shopify.http_timeout,
152
+ )
153
+ data = response.parsed_response
154
+ next_link = nil
155
+ if response.headers.key?("link")
156
+ links = Webhookdb::Shopify.parse_link_header(response.headers["link"])
157
+ next_link = links[:next] if links.key?(:next)
158
+ end
159
+ return data[self._mixin_backfill_hashkey], next_link
160
+ end
161
+ end