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,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/signalwire"
4
+
5
+ class Webhookdb::Replicator::SignalwireMessageV1 < 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: "signalwire_message_v1",
12
+ ctor: ->(sint) { Webhookdb::Replicator::SignalwireMessageV1.new(sint) },
13
+ feature_roles: [],
14
+ resource_name_singular: "SignalWire Message",
15
+ supports_backfill: true,
16
+ api_docs_url: "https://developer.signalwire.com/compatibility-api/rest/list-all-messages",
17
+ )
18
+ end
19
+
20
+ def _webhook_response(request)
21
+ auth = request.get_header("Authorization")
22
+ if auth.nil? || !auth.match(/^Basic /)
23
+ return Webhookdb::WebhookResponse.new(
24
+ status: 401,
25
+ body: "",
26
+ reason: "challenge",
27
+ headers: {"Content-Type" => "text/plain", "WWW-Authenticate" => 'Basic realm="Webhookdb"'},
28
+ )
29
+ end
30
+ user_and_pass = Base64.decode64(auth.gsub(/^Basic /, ""))
31
+ if user_and_pass != self.service_integration.webhook_secret
32
+ return Webhookdb::WebhookResponse.new(
33
+ status: 401,
34
+ body: "",
35
+ reason: "invalid",
36
+ headers: {"Content-Type" => "text/plain"},
37
+ )
38
+ end
39
+ return Webhookdb::WebhookResponse.new(
40
+ status: 202,
41
+ headers: {"Content-Type" => "text/xml"},
42
+ body: "<Response></Response>",
43
+ )
44
+ end
45
+
46
+ def calculate_backfill_state_machine
47
+ step = Webhookdb::Replicator::StateMachineStep.new
48
+ unless self.service_integration.api_url.present?
49
+ step.output = %(Let's finish setting up your SignalWire Messaging (SMS) integration.
50
+
51
+ Rather than using your phone number's webhooks (of which each number can have only one),
52
+ we poll SignalWire for changes, and will also backfill historical messages.
53
+
54
+ To do this, we need your Space URL, Project ID, and an API Token.
55
+
56
+ First enter your Space URL. You can see this on your SignalWire dashboard.
57
+ It's the part of your dashboard URL before '.signalwire.com'.)
58
+ return step.prompting("Space URL").api_url(self.service_integration)
59
+ end
60
+
61
+ unless self.service_integration.backfill_key.present?
62
+ step.output = %(You can get your Project ID from the 'API' section of your SignalWire dashboard.
63
+
64
+ Go to https://#{self.service_integration.api_url}.signalwire.com/credentials and copy your Project ID.)
65
+ return step.prompting("Project ID").backfill_key(self.service_integration)
66
+ end
67
+
68
+ unless self.service_integration.backfill_secret.present?
69
+ step.needs_input = true
70
+ step.output = %(Let's create or reuse an API token. Press the 'New' button on your dashboard,
71
+ name the token something like 'WebhookDB', and under Scopes, ensure the 'Messaging' checkbox is checked.
72
+ Then press 'Save'.
73
+
74
+ Press 'Show' next to the newly-created API token, and copy it.)
75
+ return step.secret_prompt("API Token").backfill_secret(self.service_integration)
76
+ end
77
+
78
+ unless (result = self.verify_backfill_credentials).verified
79
+ self.service_integration.replicator.clear_backfill_information
80
+ step.output = result.message
81
+ return step.secret_prompt("API Key").backfill_key(self.service_integration)
82
+ end
83
+
84
+ step.output = %(We are going to start replicating your SignalWire Messages, and will keep it updated.
85
+ #{self._query_help_output}
86
+ )
87
+ return step.completed
88
+ end
89
+
90
+ def _verify_backfill_401_err_msg
91
+ return "It looks like that API Key is invalid. Please reenter the API Key you just created:"
92
+ end
93
+
94
+ def _verify_backfill_err_msg
95
+ return "An error occurred. Please reenter the API Key you just created:"
96
+ end
97
+
98
+ def _remote_key_column
99
+ return Webhookdb::Replicator::Column.new(:signalwire_id, TEXT, data_key: "sid")
100
+ end
101
+
102
+ def _denormalized_columns
103
+ return [
104
+ Webhookdb::Replicator::Column.new(
105
+ :date_created,
106
+ TIMESTAMP,
107
+ index: true,
108
+ converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
109
+ ),
110
+ Webhookdb::Replicator::Column.new(
111
+ :date_sent,
112
+ TIMESTAMP,
113
+ index: true,
114
+ converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
115
+ ),
116
+ Webhookdb::Replicator::Column.new(
117
+ :date_updated,
118
+ TIMESTAMP,
119
+ index: true,
120
+ converter: Webhookdb::Replicator::Column::CONV_PARSE_TIME,
121
+ ),
122
+ Webhookdb::Replicator::Column.new(:direction, TEXT),
123
+ Webhookdb::Replicator::Column.new(:from, TEXT, index: true),
124
+ Webhookdb::Replicator::Column.new(:status, TEXT),
125
+ Webhookdb::Replicator::Column.new(:to, TEXT, index: true),
126
+ ]
127
+ end
128
+
129
+ def _timestamp_column_name
130
+ return :date_updated
131
+ end
132
+
133
+ def _resource_and_event(request)
134
+ return request.body, nil
135
+ end
136
+
137
+ def _update_where_expr
138
+ return self.qualified_table_sequel_identifier[:date_updated] < Sequel[:excluded][:date_updated]
139
+ end
140
+
141
+ def _fetch_backfill_page(pagination_token, last_backfilled:)
142
+ url = "https://#{self.service_integration.api_url}.signalwire.com"
143
+ if pagination_token.blank?
144
+ date_send_max = Date.tomorrow
145
+ url += "/2010-04-01/Accounts/#{self.service_integration.backfill_key}/Messages.json" \
146
+ "?PageSize=100&DateSend%3C=#{date_send_max}"
147
+ else
148
+ url += pagination_token
149
+ end
150
+ response = Webhookdb::Http.get(
151
+ url,
152
+ basic_auth: {username: self.service_integration.backfill_key,
153
+ password: self.service_integration.backfill_secret,},
154
+ logger: self.logger,
155
+ timeout: Webhookdb::Signalwire.http_timeout,
156
+ )
157
+ data = response.parsed_response
158
+ messages = data["messages"]
159
+
160
+ if last_backfilled.present?
161
+ earliest_data_created = messages.empty? ? Time.at(0) : messages[-1].fetch("date_created")
162
+ paged_to_already_seen_records = earliest_data_created < last_backfilled
163
+
164
+ return messages, nil if paged_to_already_seen_records
165
+ end
166
+
167
+ return messages, data["next_page_uri"]
168
+ end
169
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/replicator/sponsy_v1_mixin"
4
+
5
+ class Webhookdb::Replicator::SponsyCustomerV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+ include Webhookdb::Replicator::SponsyV1Mixin
8
+
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "sponsy_customer_v1",
12
+ ctor: self,
13
+ feature_roles: [],
14
+ resource_name_singular: "Sponsy Customer",
15
+ dependency_descriptor: Webhookdb::Replicator::SponsySlotV1.descriptor,
16
+ supports_backfill: true,
17
+ api_docs_url: "https://api.getsponsy.com/docs",
18
+ )
19
+ end
20
+
21
+ def _denormalized_columns
22
+ return [
23
+ Webhookdb::Replicator::Column.new(:name, TEXT),
24
+ Webhookdb::Replicator::Column.new(:logo, TEXT, optional: true),
25
+ Webhookdb::Replicator::Column.new(:notes, TEXT, optional: true),
26
+ Webhookdb::Replicator::Column.new(:portal_text, TEXT, data_key: "portalText", optional: true),
27
+ Webhookdb::Replicator::Column.new(:portal_id, TEXT, data_key: "portalId", index: true, optional: true),
28
+ ].concat(self._ts_columns)
29
+ end
30
+
31
+ def _backfillers
32
+ return [Backfiller.new(service: self)]
33
+ end
34
+
35
+ class Backfiller < Webhookdb::Backfiller
36
+ def initialize(service:)
37
+ @service = service
38
+ @slot_service = service.service_integration.depends_on.replicator
39
+ super()
40
+ end
41
+
42
+ def handle_item(body)
43
+ @service.upsert_webhook_body(body)
44
+ end
45
+
46
+ def fetch_backfill_page(_pagination_token, last_backfilled:)
47
+ customers = @slot_service.admin_dataset do |ds|
48
+ (ds = ds.where { updated_at > last_backfilled }) if last_backfilled
49
+ ds.select_map(Sequel.pg_json(:data)["customer"].as(:customer))
50
+ end
51
+ return customers, nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/replicator/sponsy_v1_mixin"
4
+
5
+ class Webhookdb::Replicator::SponsyPlacementV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+ include Webhookdb::Replicator::SponsyV1Mixin
8
+
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "sponsy_placement_v1",
12
+ ctor: self,
13
+ feature_roles: [],
14
+ resource_name_singular: "Sponsy Placement",
15
+ dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
16
+ supports_backfill: true,
17
+ api_docs_url: "https://api.getsponsy.com/docs",
18
+ )
19
+ end
20
+
21
+ def _denormalized_columns
22
+ return [
23
+ Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
24
+ Webhookdb::Replicator::Column.new(:name, TEXT),
25
+ Webhookdb::Replicator::Column.new(:slug, TEXT),
26
+ Webhookdb::Replicator::Column.new(:color, TEXT),
27
+ Webhookdb::Replicator::Column.new(:order, INTEGER),
28
+ ].concat(self._ts_columns)
29
+ end
30
+
31
+ def _backfillers(publication_ids: nil, publication_slugs: nil)
32
+ return self._publication_backfillers("/placements", publication_ids:, publication_slugs:)
33
+ end
34
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/replicator/sponsy_v1_mixin"
4
+
5
+ class Webhookdb::Replicator::SponsyPublicationV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+ include Webhookdb::Replicator::SponsyV1Mixin
8
+
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "sponsy_publication_v1",
12
+ ctor: self,
13
+ feature_roles: [],
14
+ resource_name_singular: "Sponsy Publication",
15
+ supports_backfill: true,
16
+ api_docs_url: "https://api.getsponsy.com/docs",
17
+ )
18
+ end
19
+
20
+ def _denormalized_columns
21
+ col = Webhookdb::Replicator::Column
22
+ return [
23
+ col.new(:name, TEXT),
24
+ col.new(:slug, TEXT),
25
+ col.new(:type, TEXT),
26
+ col.new(:deleted_at, TIMESTAMP, optional: true),
27
+ col.new(
28
+ :days,
29
+ INTEGER_ARRAY,
30
+ converter: col.converter_map_lookup(
31
+ array: true,
32
+ # 'MONDAY' => 0, 0 defaults to 0
33
+ map: col::DAYS_OF_WEEK.rotate.each_with_index.to_h { |dow, idx| [dow, idx] },
34
+ ),
35
+ ),
36
+ col.new(
37
+ :days_normalized,
38
+ INTEGER_ARRAY,
39
+ data_key: "days",
40
+ converter: col.converter_map_lookup(
41
+ array: true,
42
+ # 'MONDAY' => 1, 0 => 1
43
+ map: col::DAYS_OF_WEEK.each_with_index.to_a.concat((0..6).zip((0..6).to_a.rotate)).to_h,
44
+ ),
45
+ backfill_statement: Sequel.lit(<<~SQL),
46
+ CREATE OR REPLACE FUNCTION pg_temp.sponsy_publication_v1_normalize_days(integer[])
47
+ RETURNS integer[] AS 'SELECT ARRAY(SELECT ((n + 1) % 7) FROM unnest($1) AS n)' LANGUAGE sql IMMUTABLE
48
+ SQL
49
+ backfill_expr: Sequel.lit("pg_temp.sponsy_publication_v1_normalize_days(days)"),
50
+ ),
51
+ col.new(
52
+ :day_names,
53
+ TEXT_ARRAY,
54
+ data_key: "days",
55
+ converter: col.converter_map_lookup(
56
+ array: true,
57
+ # 0 => 'MONDAY'
58
+ map: col::DAYS_OF_WEEK.rotate.each_with_index.to_h { |dow, idx| [idx, dow] },
59
+ ),
60
+ # Big switch statement to map dow number to name
61
+ backfill_statement: Sequel.lit(<<~SQL),
62
+ CREATE OR REPLACE FUNCTION pg_temp.sponsy_publication_v1_day_names(integer[])
63
+ RETURNS text[] AS 'SELECT ARRAY(SELECT (CASE WHEN n = 0 THEN ''MONDAY'' WHEN n = 1 THEN ''TUESDAY'' WHEN n = 2 THEN ''WEDNESDAY'' WHEN n = 3 THEN ''THURSDAY'' WHEN n = 4 THEN ''FRIDAY'' WHEN n = 5 THEN ''SATURDAY'' WHEN n = 6 THEN ''SUNDAY'' END) FROM unnest($1) AS n)' LANGUAGE sql IMMUTABLE
64
+ SQL
65
+ backfill_expr: Sequel.lit("pg_temp.sponsy_publication_v1_day_names(days)"),
66
+ ),
67
+ ].concat(self._ts_columns)
68
+ end
69
+
70
+ def _backfillers
71
+ return [Backfiller.new(self)]
72
+ end
73
+
74
+ def _fetch_backfill_page(pagination_token, last_backfilled:)
75
+ return self.fetch_sponsy_page("/v1/publications", pagination_token, last_backfilled)
76
+ end
77
+
78
+ def calculate_backfill_state_machine
79
+ step = Webhookdb::Replicator::StateMachineStep.new
80
+ unless self.service_integration.backfill_secret.present?
81
+ step.needs_input = true
82
+ step.output = %(Great! Let's work on your Sponsy Publications integration.
83
+
84
+ Head over to your Sponsy dashboard and copy your API key:
85
+
86
+ https://getsponsy.com/settings/workspace
87
+ )
88
+ return step.secret_prompt("API key").backfill_secret(self.service_integration)
89
+ end
90
+
91
+ unless (result = self.verify_backfill_credentials).verified
92
+ self.service_integration.replicator.clear_backfill_information
93
+ step.output = result.message
94
+ return step.secret_prompt("API Key").backfill_secret(self.service_integration)
95
+ end
96
+
97
+ step.output = %(We are going to start replicating your Sponsy Publications
98
+ and will keep them updated. You can can also add more Sponsy integrations.
99
+ Run `webhookdb services list` to see what's available.
100
+ #{self._query_help_output}
101
+ )
102
+ return step.completed
103
+ end
104
+
105
+ def _verify_backfill_401_err_msg
106
+ return "It looks like that API Key is invalid. Head back to https://getsponsy.com/settings/workspace, " \
107
+ "copy the API key, and try again:"
108
+ end
109
+
110
+ # Normal backfiller that keeps track of inserted items,
111
+ # and marks anything not backfilled as deleted.
112
+ class Backfiller < Webhookdb::Replicator::Base::ServiceBackfiller
113
+ def handle_item(item)
114
+ super
115
+ @seen_ids ||= []
116
+ @seen_ids << item.fetch("id")
117
+ end
118
+
119
+ def flush_pending_inserts
120
+ self.svc.admin_dataset do |ds|
121
+ ds.exclude(sponsy_id: @seen_ids).where(deleted_at: nil).update(deleted_at: Sequel.function(:now))
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/replicator/sponsy_v1_mixin"
4
+
5
+ class Webhookdb::Replicator::SponsySlotV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+ include Webhookdb::Replicator::SponsyV1Mixin
8
+
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "sponsy_slot_v1",
12
+ ctor: self,
13
+ feature_roles: [],
14
+ resource_name_singular: "Sponsy Slot",
15
+ dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
16
+ supports_backfill: true,
17
+ api_docs_url: "https://api.getsponsy.com/docs",
18
+ )
19
+ end
20
+
21
+ def _denormalized_columns
22
+ return [
23
+ Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
24
+ Webhookdb::Replicator::Column.new(:date, DATE, index: true),
25
+ Webhookdb::Replicator::Column.new(:notes, TEXT),
26
+ Webhookdb::Replicator::Column.new(
27
+ :customer_id, TEXT, data_key: ["customer", "id"], optional: true, index: true,
28
+ ),
29
+ Webhookdb::Replicator::Column.new(
30
+ :placement_id, TEXT, data_key: ["placement", "id"], optional: true, index: true,
31
+ ),
32
+ Webhookdb::Replicator::Column.new(
33
+ :status_id, TEXT, data_key: ["status", "id"], optional: true, index: true,
34
+ ),
35
+ ].concat(self._ts_columns)
36
+ end
37
+
38
+ def _backfillers(publication_ids: nil, publication_slugs: nil)
39
+ return self._publication_backfillers("/slots", publication_ids:, publication_slugs:)
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/replicator/sponsy_v1_mixin"
4
+
5
+ class Webhookdb::Replicator::SponsyStatusV1 < Webhookdb::Replicator::Base
6
+ include Appydays::Loggable
7
+ include Webhookdb::Replicator::SponsyV1Mixin
8
+
9
+ def self.descriptor
10
+ return Webhookdb::Replicator::Descriptor.new(
11
+ name: "sponsy_status_v1",
12
+ ctor: self,
13
+ feature_roles: [],
14
+ resource_name_singular: "Sponsy Status",
15
+ resource_name_plural: "Sponsy Statuses",
16
+ dependency_descriptor: Webhookdb::Replicator::SponsyPublicationV1.descriptor,
17
+ supports_backfill: true,
18
+ api_docs_url: "https://api.getsponsy.com/docs",
19
+ )
20
+ end
21
+
22
+ def _denormalized_columns
23
+ return [
24
+ Webhookdb::Replicator::Column.new(:publication_id, TEXT, index: true),
25
+ Webhookdb::Replicator::Column.new(:name, TEXT),
26
+ Webhookdb::Replicator::Column.new(:slug, TEXT),
27
+ Webhookdb::Replicator::Column.new(:color, TEXT),
28
+ Webhookdb::Replicator::Column.new(:order, INTEGER),
29
+ ].concat(self._ts_columns)
30
+ end
31
+
32
+ def _backfillers(publication_ids: nil, publication_slugs: nil)
33
+ return self._publication_backfillers("/status", publication_ids:, publication_slugs:)
34
+ end
35
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/sponsy"
4
+
5
+ module Webhookdb::Replicator::SponsyV1Mixin
6
+ include Webhookdb::DBAdapter::ColumnTypes
7
+
8
+ def _remote_key_column
9
+ return Webhookdb::Replicator::Column.new(:sponsy_id, TEXT, data_key: "id")
10
+ end
11
+
12
+ def _timestamp_column_name
13
+ return :updated_at
14
+ end
15
+
16
+ def _ts_columns
17
+ return [
18
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "createdAt"),
19
+ Webhookdb::Replicator::Column.new(
20
+ :updated_at, TIMESTAMP,
21
+ data_key: "updatedAt",
22
+ defaulter: Webhookdb::Replicator::Column.defaulter_from_resource_field(:created_at),
23
+ ),
24
+ ]
25
+ end
26
+
27
+ def _verify_backfill_err_msg
28
+ return "Looks like your API key is invalid."
29
+ end
30
+
31
+ def api_url
32
+ return "https://api.getsponsy.com"
33
+ end
34
+
35
+ def auth_headers
36
+ return {"X-Api-Key" => self.find_api_key}
37
+ end
38
+
39
+ def root_integration
40
+ return @root_integration ||= Webhookdb::Replicator.find_at_root!(self.service_integration,
41
+ service_name: "sponsy_publication_v1",)
42
+ end
43
+
44
+ def find_api_key
45
+ return self.root_integration.backfill_secret
46
+ end
47
+
48
+ def _resource_and_event(request)
49
+ return request.body, nil
50
+ end
51
+
52
+ def _update_where_expr
53
+ return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
54
+ end
55
+
56
+ def _webhook_response(_request)
57
+ # There are no webhooks to respond to, these are backfill-only integrations
58
+ return Webhookdb::WebhookResponse.ok
59
+ end
60
+
61
+ # @return [Webhookdb::Replicator::StateMachineStep]
62
+ def calculate_backfill_state_machine
63
+ check_dep = self.class.descriptor.dependency_descriptor
64
+ if check_dep && (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
65
+ return step
66
+ end
67
+ step = Webhookdb::Replicator::StateMachineStep.new
68
+ step.output = %(We will start replicating #{self.resource_name_plural} into your WebhookDB database.
69
+
70
+ #{self._query_help_output(prefix: "Once data is available, you can query #{self.resource_name_plural}.")})
71
+ return step.completed
72
+ end
73
+
74
+ def on_dependency_webhook_upsert(_replicator, _payload, *)
75
+ return
76
+ end
77
+
78
+ def _parallel_backfill = Webhookdb::Sponsy.parallel_backfill
79
+
80
+ # Paginate from most recently updated.
81
+ # We paginate until either:
82
+ # - There are no more pages (the 'after cursor' is nil), or
83
+ # - the updated at timestamp predates the time we last backfilled,
84
+ # meaning we probably already saw this update.
85
+ def fetch_sponsy_page(tail, pagination_token, last_backfilled)
86
+ url = self.api_url + tail
87
+ begin
88
+ response = Webhookdb::Http.get(
89
+ url,
90
+ query: {
91
+ limit: Webhookdb::Sponsy.page_size.to_s,
92
+ afterCursor: pagination_token,
93
+ orderBy: "updatedAt",
94
+ orderDirection: "DESC",
95
+ },
96
+ headers: self.auth_headers,
97
+ logger: self.logger,
98
+ timeout: Webhookdb::Sponsy.http_timeout,
99
+ )
100
+ rescue Webhookdb::Http::Error => e
101
+ raise e unless e.status == 404
102
+ self.logger.warn("sponsy_404", error: e)
103
+ return [], nil
104
+ end
105
+
106
+ data = response.parsed_response.fetch("data")
107
+ after_cursor = response.parsed_response.fetch("cursor", {}).fetch("afterCursor", nil)
108
+ return data, nil if after_cursor.nil?
109
+ return [], nil if data.empty?
110
+ last_updated = data.last.fetch("updatedAt")
111
+ return data, nil if last_updated < (last_backfilled || Time.at(0))
112
+ return data, after_cursor
113
+ end
114
+
115
+ def _publication_backfillers(tail, publication_ids: nil, publication_slugs: nil)
116
+ raise Webhookdb::Replicator::CredentialsMissing, "This Sponsy integration is missing a dependency with auth" if
117
+ self.find_api_key.blank?
118
+
119
+ publications_svc = self.service_integration.depends_on.replicator
120
+ backfillers = publications_svc.readonly_dataset(timeout: :fast) do |pub_ds|
121
+ pub_ds = Webhookdb::Dbutil.reduce_expr(
122
+ pub_ds,
123
+ :|,
124
+ [publication_ids && Sequel[sponsy_id: publication_ids], publication_slugs && Sequel[slug: publication_slugs]],
125
+ )
126
+ pub_ds = pub_ds.where(deleted_at: nil)
127
+ pub_ds.select(:sponsy_id).map do |publication|
128
+ PublicationChildBackfiller.new(
129
+ service: self,
130
+ publication_id: publication.fetch(:sponsy_id),
131
+ tail:,
132
+ )
133
+ end
134
+ end
135
+ return backfillers
136
+ end
137
+
138
+ class PublicationChildBackfiller < Webhookdb::Backfiller
139
+ include Webhookdb::Backfiller::Bulk
140
+
141
+ attr_reader :upserting_replicator
142
+
143
+ def initialize(service:, publication_id:, tail:)
144
+ @service = service
145
+ @upserting_replicator = @service
146
+ @publication_id = publication_id
147
+ @tail = tail
148
+ super()
149
+ end
150
+
151
+ def upsert_page_size = 500
152
+ def conditional_upsert? = true
153
+
154
+ def prepare_body(body)
155
+ body["publication_id"] = @publication_id
156
+ body
157
+ end
158
+
159
+ def fetch_backfill_page(pagination_token, last_backfilled:)
160
+ return @service.fetch_sponsy_page(
161
+ "/v1/publications/#{@publication_id}#{@tail}", pagination_token, last_backfilled,
162
+ )
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Replicator::StateMachineStep
4
+ attr_accessor :needs_input,
5
+ :prompt,
6
+ :prompt_is_secret,
7
+ :post_to_url,
8
+ :complete,
9
+ :output,
10
+ :error_code,
11
+ :post_params,
12
+ :post_params_value_key
13
+
14
+ def initialize
15
+ @needs_input = false
16
+ @prompt = ""
17
+ @prompt_is_secret = false
18
+ @post_to_url = ""
19
+ @complete = false
20
+ @output = ""
21
+ @error_code = ""
22
+ @post_params = {}
23
+ @post_params_value_key = "value"
24
+ end
25
+
26
+ def successful? = return self.complete && self.error_code.blank?
27
+
28
+ # @return [Webhookdb::Replicator::StateMachineStep]
29
+ def completed
30
+ self.complete = true
31
+ self.needs_input = false
32
+ return self
33
+ end
34
+
35
+ # @return [Webhookdb::Replicator::StateMachineStep]
36
+ def secret_prompt(field)
37
+ return self.prompting(field, secret: true)
38
+ end
39
+
40
+ def prompting(field, secret: false)
41
+ return self.set_prompt("Paste or type your #{field} here:", secret:)
42
+ end
43
+
44
+ def set_prompt(value, secret: false)
45
+ self.needs_input = true
46
+ self.prompt = value
47
+ self.prompt_is_secret = secret
48
+ self.complete = false
49
+ return self
50
+ end
51
+
52
+ # @return [Webhookdb::Replicator::StateMachineStep]
53
+ def backfill_secret(sint) = self.transition_field(sint, "backfill_secret")
54
+
55
+ # @return [Webhookdb::Replicator::StateMachineStep]
56
+ def backfill_key(sint) = self.transition_field(sint, "backfill_key")
57
+
58
+ # @return [Webhookdb::Replicator::StateMachineStep]
59
+ def webhook_secret(sint) = self.transition_field(sint, "webhook_secret")
60
+
61
+ # @return [Webhookdb::Replicator::StateMachineStep]
62
+ def api_url(sint) = self.transition_field(sint, "api_url")
63
+
64
+ # @return [Webhookdb::Replicator::StateMachineStep]
65
+ def transition_field(sint, field)
66
+ self.post_to_url = sint.authed_api_path + "/transition/#{field}"
67
+ return self
68
+ end
69
+ end