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,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "webhookdb/convertkit"
5
+ require "webhookdb/replicator/convertkit_v1_mixin"
6
+
7
+ class Webhookdb::Replicator::ConvertkitSubscriberV1 < Webhookdb::Replicator::Base
8
+ include Appydays::Loggable
9
+ include Webhookdb::Replicator::ConvertkitV1Mixin
10
+
11
+ # @return [Webhookdb::Replicator::Descriptor]
12
+ def self.descriptor
13
+ return Webhookdb::Replicator::Descriptor.new(
14
+ name: "convertkit_subscriber_v1",
15
+ ctor: ->(sint) { Webhookdb::Replicator::ConvertkitSubscriberV1.new(sint) },
16
+ feature_roles: [],
17
+ resource_name_singular: "ConvertKit Subscriber",
18
+ supports_webhooks: true,
19
+ supports_backfill: true,
20
+ description: "Replicate ConvertKit subscribers into a database. " \
21
+ "This is one of the only ways you can keep track of historical subscriber information " \
22
+ "with ConvertKit.",
23
+ api_docs_url: "https://developers.convertkit.com/#list-subscribers",
24
+ )
25
+ end
26
+
27
+ def process_state_change(field, value)
28
+ step = super
29
+ self._create_webhooks if field == "backfill_secret"
30
+ return step
31
+ end
32
+
33
+ def _create_webhooks
34
+ # ConvertKit has made several other webhooks available for the subscriber object, but they all have required
35
+ # parameters that are pks of other objects that webhookdb knows nothing about.
36
+
37
+ # first verify that the webhooks don't exist
38
+ url = "https://api.convertkit.com/v3/automations/hooks?api_secret=#{self.service_integration.backfill_secret}"
39
+ response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
40
+ # the data returned here is a list of the existing webhooks
41
+ data = response.parsed_response
42
+
43
+ # does the "subscriber.subscriber_activate" exist? if not, create it
44
+ # rubocop:disable Style/GuardClause
45
+ if data.present?
46
+ sub_activate_webhook = data.find do |obj|
47
+ obj.dig("rule", "event", "name") == "subscriber_activate"
48
+ end
49
+ end
50
+ unless sub_activate_webhook.present?
51
+ Webhookdb::Http.post(
52
+ "https://api.convertkit.com/v3/automations/hooks",
53
+ {
54
+ "api_secret" => self.service_integration.backfill_secret,
55
+ "target_url" => self.webhook_endpoint,
56
+ "event" => {"name" => "subscriber.subscriber_activate"},
57
+ },
58
+ logger: self.logger,
59
+ timeout: Webhookdb::Convertkit.http_timeout,
60
+ )
61
+ end
62
+
63
+ # does the "subscriber.subscriber_activate" exist? if not, create it
64
+ if data.present?
65
+ sub_unsubscribe_webhook = data.find do |obj|
66
+ obj.dig("rule", "event", "name") == "subscriber_activate"
67
+ end
68
+ end
69
+ unless sub_unsubscribe_webhook.present?
70
+ Webhookdb::Http.post(
71
+ "https://api.convertkit.com/v3/automations/hooks",
72
+ {
73
+ "api_secret" => self.service_integration.backfill_secret,
74
+ "target_url" => self.webhook_endpoint,
75
+ "event" => {"name" => "subscriber.subscriber_unsubscribe"},
76
+ },
77
+ logger: self.logger,
78
+ timeout: Webhookdb::Convertkit.http_timeout,
79
+ )
80
+ end
81
+ # rubocop:enable Style/GuardClause
82
+ end
83
+
84
+ def calculate_webhook_state_machine
85
+ step = Webhookdb::Replicator::StateMachineStep.new
86
+ step.output = %(
87
+ Great! We've created your ConvertKit Subscribers integration.
88
+
89
+ ConvertKit supports Subscriber webhooks.
90
+ You have two options for hooking them up:
91
+
92
+ A: Create the webhook yourself, so you don't need to provide us an API Secret.
93
+ Run this from a shell:
94
+
95
+ curl -X POST https://api.convertkit.com/v3/automations/hooks
96
+ -H 'Content-Type: application/json'\\
97
+ -d '{ "api_secret": "<your_secret_api_key>",\\
98
+ "target_url": "#{self._webhook_endpoint}",\\
99
+ "event": { "name": "subscriber.subscriber_activate" } }'
100
+ curl -X POST https://api.convertkit.com/v3/automations/hooks
101
+ -H 'Content-Type: application/json'\\
102
+ -d '{ "api_secret": "<your_secret_api_key>",\\
103
+ "target_url": "#{self._webhook_endpoint}",\\
104
+ "event": { "name": "subscriber.subscriber_unsubscribe" } }'
105
+
106
+ B: Use WebhookDB to backfill historical data with your API Secret, and when we do this,
107
+ we'll also set up webhooks for new data.
108
+ To start backfilling historical data, run this from a shell:
109
+
110
+ #{self._backfill_command}
111
+
112
+ Once you have data (you set up the webhooks, or ran the 'backfill' command),
113
+ your database will be populated.
114
+ #{self._query_help_output}
115
+ )
116
+ return step.completed
117
+ end
118
+
119
+ def _denormalized_columns
120
+ return [
121
+ Webhookdb::Replicator::Column.new(
122
+ :canceled_at,
123
+ TIMESTAMP,
124
+ index: true,
125
+ optional: true,
126
+ converter: CONV_FIND_CANCELED_AT,
127
+ ),
128
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "created_at", index: true),
129
+ Webhookdb::Replicator::Column.new(:email_address, TEXT, index: true),
130
+ Webhookdb::Replicator::Column.new(:first_name, TEXT),
131
+ Webhookdb::Replicator::Column.new(:last_name, TEXT, data_key: ["fields", "last_name"], optional: true),
132
+ Webhookdb::Replicator::Column.new(:state, TEXT),
133
+ ]
134
+ end
135
+
136
+ def _timestamp_column_name
137
+ return :created_at
138
+ end
139
+
140
+ def _resource_and_event(request)
141
+ body = request.body
142
+ return body["subscriber"], body if body.key?("subscriber").present?
143
+ return body, nil
144
+ end
145
+
146
+ def _update_where_expr
147
+ return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
148
+ end
149
+
150
+ def _upsert_update_expr(inserting, **_kwargs)
151
+ update = super
152
+ state = inserting.fetch(:state)
153
+ # If the state is active, we want to use canceled_at:nil unconditionally.
154
+ return update if state == "active"
155
+ # If it's inactive, we only want to update canceled_at if it's not already set
156
+ # (coalesce the existing row's canceled_at with the 'time.now' we are passing in).
157
+ self._coalesce_excluded_on_update(update, [:canceled_at])
158
+ return update
159
+ end
160
+
161
+ def _fetch_backfill_page(pagination_token, last_backfilled:)
162
+ pagination_token ||= ["subscribed", 1]
163
+ list_being_iterated, page = pagination_token
164
+
165
+ url = "https://api.convertkit.com/v3/subscribers?api_secret=#{self.service_integration.backfill_secret}&page=#{page}&sort_order=desc"
166
+ url += "&updated_from=#{last_backfilled.strftime('%FT%TZ')}" if last_backfilled.present?
167
+ url += "&sort_field=cancelled_at" if list_being_iterated == "cancelled"
168
+
169
+ response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
170
+ data = response.parsed_response
171
+ current_page = data["page"]
172
+ total_pages = data["total_pages"]
173
+ subs = data["subscribers"]
174
+
175
+ if last_backfilled.present?
176
+ earliest_data_created = subs.empty? ? Time.at(0) : subs[-1].fetch("created_at")
177
+ paged_to_already_seen_records = earliest_data_created < last_backfilled
178
+
179
+ if paged_to_already_seen_records && list_being_iterated == "subscribed"
180
+ # If we are done backfilling from the 'subscribed' list, we can now iterate cancelled
181
+ return subs, ["cancelled", 1]
182
+ end
183
+ if paged_to_already_seen_records && list_being_iterated == "cancelled"
184
+ # If we are done backfilling from the 'cancelled' list, we are done backfilling
185
+ return subs, nil
186
+ end
187
+ end
188
+
189
+ if current_page < total_pages
190
+ # If we still have pages on this list, go to the next one
191
+ return subs, [list_being_iterated, current_page + 1]
192
+ end
193
+ if list_being_iterated == "subscribed"
194
+ # If we are done with the 'subscribed' list, we can now iterate cancelled
195
+ return subs, ["cancelled", 1]
196
+ end
197
+ # Otherwise, we're at the last page of our canceled subscribers list
198
+ return subs, nil
199
+ end
200
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "webhookdb/convertkit"
5
+ require "webhookdb/replicator/convertkit_v1_mixin"
6
+
7
+ class Webhookdb::Replicator::ConvertkitTagV1 < Webhookdb::Replicator::Base
8
+ include Appydays::Loggable
9
+ include Webhookdb::Replicator::ConvertkitV1Mixin
10
+
11
+ # @return [Webhookdb::Replicator::Descriptor]
12
+ def self.descriptor
13
+ return Webhookdb::Replicator::Descriptor.new(
14
+ name: "convertkit_tag_v1",
15
+ ctor: ->(sint) { Webhookdb::Replicator::ConvertkitTagV1.new(sint) },
16
+ feature_roles: [],
17
+ resource_name_singular: "ConvertKit Tag",
18
+ supports_backfill: true,
19
+ api_docs_url: "https://developers.convertkit.com/#list-tags",
20
+ )
21
+ end
22
+
23
+ def _denormalized_columns
24
+ return [
25
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "created_at", index: true),
26
+ Webhookdb::Replicator::Column.new(:name, TEXT, index: true),
27
+ Webhookdb::Replicator::Column.new(:total_subscriptions, INTEGER, from_enrichment: true),
28
+ ]
29
+ end
30
+
31
+ def _timestamp_column_name
32
+ return :created_at
33
+ end
34
+
35
+ def _resource_and_event(request)
36
+ return request.body, nil
37
+ end
38
+
39
+ def _update_where_expr
40
+ return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
41
+ end
42
+
43
+ def upsert_has_deps?
44
+ return true
45
+ end
46
+
47
+ def _store_enrichment_body?
48
+ return true
49
+ end
50
+
51
+ def _fetch_enrichment(resource, _event, _request)
52
+ tag_id = resource.fetch("id")
53
+ url = "https://api.convertkit.com/v3/tags/#{tag_id}/subscriptions?api_secret=#{self.service_integration.backfill_secret}"
54
+ response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
55
+ data = response.parsed_response
56
+ return data
57
+ end
58
+
59
+ def _fetch_backfill_page(_pagination_token, **_kwargs)
60
+ # this endpoint does not have pagination support
61
+ url = "https://api.convertkit.com/v3/tags?api_secret=#{self.service_integration.backfill_secret}"
62
+ response = Webhookdb::Http.get(url, logger: self.logger, timeout: Webhookdb::Convertkit.http_timeout)
63
+ data = response.parsed_response
64
+ return data["tags"], nil
65
+ end
66
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Webhookdb::Replicator::ConvertkitV1Mixin
4
+ include Webhookdb::DBAdapter::ColumnTypes
5
+
6
+ # ConvertKit gets a decent number of 5xx responses.
7
+ # Wait for about 20 minutes before the job dies.
8
+ def backfiller_server_error_retries = 10
9
+ def backfiller_server_error_backoff = 121.seconds
10
+
11
+ def _webhook_response(_request)
12
+ # Webhook Authentication isn't supported
13
+ return Webhookdb::WebhookResponse.ok
14
+ end
15
+
16
+ def _remote_key_column
17
+ return Webhookdb::Replicator::Column.new(:convertkit_id, BIGINT, data_key: "id")
18
+ end
19
+
20
+ def calculate_backfill_state_machine
21
+ step = Webhookdb::Replicator::StateMachineStep.new
22
+ if self.service_integration.backfill_secret.blank?
23
+ step.output = %(Great! We've created your #{self.resource_name_plural} integration.
24
+
25
+ #{self.resource_name_singular} webhooks are not designed to mirror data, so to fill your database,
26
+ we need to use the API to make requests, which requires your API Secret.
27
+ #{Webhookdb::Convertkit::FIND_API_SECRET_HELP}
28
+ )
29
+ return step.secret_prompt("API Secret").backfill_secret(self.service_integration)
30
+ end
31
+
32
+ unless (result = self.verify_backfill_credentials).verified
33
+ self.service_integration.replicator.clear_backfill_information
34
+ step.output = result.message
35
+ return step.secret_prompt("API Secret").backfill_secret(self.service_integration)
36
+ end
37
+
38
+ step.output = %(We'll start backfilling your #{self.resource_name_plural} now,
39
+ and they will show up in your database momentarily.
40
+ #{self._query_help_output}
41
+ )
42
+ return step.completed
43
+ end
44
+
45
+ def _verify_backfill_401_err_msg
46
+ return "It looks like that API Secret is invalid. Please reenter the API Secret:"
47
+ end
48
+
49
+ def _verify_backfill_err_msg
50
+ return "An error occurred. Please reenter the API Secret:"
51
+ end
52
+
53
+ # Converter for use with the denormalized columns
54
+ CONV_FIND_CANCELED_AT = Webhookdb::Replicator::Column::IsomorphicProc.new(
55
+ ruby: lambda do |_, resource:, **_|
56
+ # Subscribers do not store a cancelation time (nor an updated at time),
57
+ # so we derive and store it based on their state.
58
+ # When they become inactive state, we set canceled_at,
59
+ # and clear it when they are not active.
60
+ # See the upsert_update_expr for Convertkit Subscriber for the details.
61
+ resource.fetch("state") == "active" ? nil : Time.now
62
+ end,
63
+ sql: nil,
64
+ )
65
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Write docs for docs.webhookdb.com Jekyll site.
4
+ class Webhookdb::Replicator::Docgen
5
+ def self.documentable_descriptors
6
+ return Webhookdb::Replicator.registry.values.reject do |repl|
7
+ repl.name.start_with?("webhookdb_", "fake_")
8
+ end.sort_by(&:name)
9
+ end
10
+
11
+ # @!attribute desc
12
+ # @return [Webhookdb::Replicator::Descriptor]
13
+ attr_reader :desc
14
+ attr_reader :lines
15
+
16
+ # @param desc [Webhookdb::Replicator::Descriptor]
17
+ def initialize(desc)
18
+ @desc = desc
19
+ @lines = []
20
+ @documentable_descriptors = self.class.documentable_descriptors
21
+ end
22
+
23
+ def sint
24
+ @sint ||= Webhookdb::ServiceIntegration.new(
25
+ service_name: desc.name,
26
+ opaque_id: "svi_fixture",
27
+ table_name: desc.name + "_fixture",
28
+ )
29
+ end
30
+
31
+ def repl
32
+ @repl ||= desc.ctor[sint]
33
+ end
34
+
35
+ def markdown
36
+ _frontmatter
37
+ _intro
38
+ _features
39
+ _schema
40
+ _tabledef
41
+ _prevnext
42
+ return lines.join("\n")
43
+ end
44
+
45
+ def _frontmatter
46
+ lines << "---"
47
+ lines << "title: #{desc.resource_name_singular}"
48
+ lines << "layout: home"
49
+ idx = @documentable_descriptors.index(desc)
50
+ lines << "nav_order: #{(idx + 1) * 10}"
51
+ lines << "---"
52
+ lines << ""
53
+ end
54
+
55
+ def _intro
56
+ lines << "# #{desc.resource_name_singular} (`#{desc.name}`)"
57
+ if desc.description.present?
58
+ lines << ""
59
+ lines << desc.description
60
+ end
61
+ if desc.api_docs_url.present?
62
+ lines << ""
63
+ lines << "Docs for this API: [#{desc.api_docs_url}](#{desc.api_docs_url})"
64
+ end
65
+ lines << ""
66
+ end
67
+
68
+ def _features
69
+ lines << "## Features"
70
+ lines << ""
71
+ lines << "<dl>"
72
+ if (dep = desc.dependency_descriptor)
73
+ lines << "<dt>Depends on</dt>"
74
+ lines << "<dd>To use this replicator, you will need #{refanchor(dep)}. " \
75
+ "You'll be prompted to create it if you haven't.</dd>"
76
+ lines << ""
77
+ end
78
+ deps = @documentable_descriptors.select { |d| d.dependency_descriptor == desc }
79
+ if deps.any?
80
+ lines << "<dt>Dependents</dt>"
81
+ lines << "<dd>This replicator is required for the creation of the following dependents:"
82
+ lines << "<ul>"
83
+ deps.each { |d| lines << "<li>#{refanchor(d)}</li>" }
84
+ lines << "</ul>"
85
+ lines << "</dd>"
86
+ lines << ""
87
+ end
88
+ lines << "<dt>Supports Webhooks</dt>"
89
+ lines << "<dd>#{boolmoji(desc.supports_webhooks?)}</dd>"
90
+ lines << "<dt>Supports Backfilling</dt>"
91
+ lines << "<dd>#{boolmoji(desc.supports_backfill?)}</dd>"
92
+ if desc.enterprise?
93
+ lines << "<dt>Enterprise Only</dt>"
94
+ lines << "<dd>Yes</dd>"
95
+ end
96
+ lines << ""
97
+ lines << "</dl>"
98
+ lines << ""
99
+ end
100
+
101
+ def _schema
102
+ lines << "## Schema"
103
+ lines << ""
104
+ lines << "Tables replicated from #{desc.resource_name_plural} have this schema.
105
+ Note that the data types listed are for Postgres;
106
+ when [replicating to other databases]({% link _concepts/replication_databases.md %}),
107
+ other data types maybe used."
108
+ lines << ""
109
+ lines << "| Column | Type | Indexed |"
110
+ columns = [repl.primary_key_column, repl.remote_key_column]
111
+ columns.concat(repl.storable_columns)
112
+ columns << repl.data_column
113
+ columns.each do |c|
114
+ name = "`#{c.name}`"
115
+ (name += "*") if c.name == :data
116
+ lines << "| #{name} | `#{pgtype(c.type)}` | #{truecheck(c.index)} |"
117
+ end
118
+ lines << ""
119
+ lines << <<~S
120
+ <span class="fs-3">* The `data` column contains the raw payload from the webhook or API.
121
+ In many cases there is no canonical form, like if a webhook and API request return
122
+ two different versions of the same resource.
123
+ In that case we try to keep the most coherent and detailed resource."</span>
124
+ S
125
+ end
126
+
127
+ def _tabledef
128
+ lines << "## Table definition"
129
+ lines << ""
130
+ lines << "This definition can also be generated through `webhookdb fixture #{desc.name}`."
131
+ lines << ""
132
+ lines << "```sql"
133
+ lines << repl.create_table_modification.to_s
134
+ lines << "```"
135
+ lines << ""
136
+ end
137
+
138
+ def _prevnext
139
+ idx = @documentable_descriptors.index(desc)
140
+ raise Webhookdb::InvariantViolation if idx.nil?
141
+ prevtxt = nexttxt = ""
142
+ if (rprev = idx.zero? ? nil : @documentable_descriptors[idx - 1])
143
+ prevtxt = "prev='_integrations/#{rprev.name}.md' prevLabel='#{rprev.name}' "
144
+ end
145
+ if (rnext = idx == (@documentable_descriptors.size - 1) ? nil : @documentable_descriptors[idx + 1])
146
+ nexttxt = "next='_integrations/#{rnext.name}.md' nextLabel='#{rnext.name}'"
147
+ end
148
+ lines << "{% include prevnext.html #{prevtxt}#{nexttxt} %}"
149
+ lines << ""
150
+ end
151
+
152
+ def refhref(d) = "{% link _integrations/#{d.name}.md %}"
153
+ def refanchor(d) = "<a href=\"#{refhref(d)}\">#{d.name}</a>"
154
+ def boolmoji(b) = b ? "✅" : "❌"
155
+ def truecheck(b) = b ? "✅" : ""
156
+ def pgtype(t) = Webhookdb::DBAdapter::PG::COLTYPE_MAP[t]
157
+
158
+ def self.replicator_list_md(descriptors)
159
+ lines = []
160
+ descriptors.each do |d|
161
+ line = "- [#{d.resource_name_singular}]({% link _integrations/#{d.name}.md %})"
162
+ line += " ([Enterprise]({% link docs/enterprise.md %}) only)" if d.enterprise
163
+ lines << line
164
+ end
165
+ return lines.join("\n")
166
+ end
167
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/email_octopus"
4
+
5
+ class Webhookdb::Replicator::EmailOctopusCampaignV1 < 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: "email_octopus_campaign_v1",
12
+ ctor: ->(sint) { Webhookdb::Replicator::EmailOctopusCampaignV1.new(sint) },
13
+ feature_roles: [],
14
+ resource_name_singular: "Email Octopus Campaign",
15
+ dependency_descriptor: Webhookdb::Replicator::EmailOctopusListV1.descriptor,
16
+ supports_backfill: true,
17
+ api_docs_url: "https://emailoctopus.com/api-documentation",
18
+ )
19
+ end
20
+
21
+ def _remote_key_column
22
+ return Webhookdb::Replicator::Column.new(:email_octopus_id, TEXT, data_key: "id")
23
+ end
24
+
25
+ def _denormalized_columns
26
+ return [
27
+ Webhookdb::Replicator::Column.new(:name, TEXT),
28
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true, converter: :time),
29
+ Webhookdb::Replicator::Column.new(:sent_at, TIMESTAMP, index: true, converter: :time),
30
+ Webhookdb::Replicator::Column.new(:status, TEXT),
31
+ Webhookdb::Replicator::Column.new(:from_name, TEXT, data_key: ["from", "name"]),
32
+ Webhookdb::Replicator::Column.new(:from_email_address, TEXT, data_key: ["from", "email_address"]),
33
+ Webhookdb::Replicator::Column.new(:subject, TEXT),
34
+ Webhookdb::Replicator::Column.new(:row_updated_at, TIMESTAMP, defaulter: :now, optional: true),
35
+ ]
36
+ end
37
+
38
+ def _resource_and_event(request)
39
+ return request.body, nil
40
+ end
41
+
42
+ def _update_where_expr
43
+ return self.qualified_table_sequel_identifier[:data] !~ Sequel[:excluded][:data]
44
+ end
45
+
46
+ def _timestamp_column_name
47
+ return :row_updated_at
48
+ end
49
+
50
+ def _webhook_response(_request)
51
+ return Webhookdb::WebhookResponse.ok
52
+ end
53
+
54
+ def on_dependency_webhook_upsert(_replicator, _payload, *)
55
+ return
56
+ end
57
+
58
+ def calculate_backfill_state_machine
59
+ if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
60
+ return step
61
+ end
62
+ step = Webhookdb::Replicator::StateMachineStep.new
63
+ # We're using the API Key from the dependency, we don't need to ask for it here
64
+ step.output = %(Great! We are going to start replicating your #{self.resource_name_plural}.
65
+ #{self._query_help_output}
66
+ )
67
+ return step.completed
68
+ end
69
+
70
+ def _fetch_backfill_page(pagination_token, **)
71
+ api_key = self.service_integration.depends_on.replicator.backfill_key!
72
+ limit = Webhookdb::EmailOctopus.page_size
73
+ base_url = "https://emailoctopus.com"
74
+ endpoint_path = pagination_token || "/api/1.6/campaigns?api_key=#{api_key}&limit=#{limit}"
75
+ response = Webhookdb::Http.get(
76
+ base_url + endpoint_path,
77
+ logger: self.logger,
78
+ timeout: Webhookdb::EmailOctopus.http_timeout,
79
+ )
80
+ data = response.parsed_response
81
+ next_page_link = data.dig("paging", "next")
82
+ return data["data"], next_page_link
83
+ end
84
+ end