webhookdb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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