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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/message/template"
4
+
5
+ class Webhookdb::Messages::NewCustomer < Webhookdb::Message::Template
6
+ def self.fixtured(recipient)
7
+ return self.new(recipient)
8
+ end
9
+
10
+ def initialize(customer)
11
+ @customer = customer
12
+ super()
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/message/template"
4
+
5
+ class Webhookdb::Messages::OrgDatabaseMigrationFinished < Webhookdb::Message::Template
6
+ def self.fixtured(recipient)
7
+ dbm = Webhookdb::Fixtures.organization_database_migration.with_urls.finished.create
8
+ Webhookdb::Fixtures.organization_membership.org(dbm.organization).customer(recipient).verified.admin.create
9
+ return self.new(dbm)
10
+ end
11
+
12
+ def initialize(org_database_migration)
13
+ @org_database_migration = org_database_migration
14
+ super()
15
+ end
16
+
17
+ def liquid_drops
18
+ return super.merge(
19
+ org_name: @org_database_migration.organization.name,
20
+ destination_host: @org_database_migration.displaysafe_destination_url,
21
+ )
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/message/template"
4
+
5
+ class Webhookdb::Messages::OrgDatabaseMigrationStarted < Webhookdb::Message::Template
6
+ def self.fixtured(recipient)
7
+ dbm = Webhookdb::Fixtures.organization_database_migration.with_urls.started.create
8
+ Webhookdb::Fixtures.organization_membership.org(dbm.organization).customer(recipient).verified.admin.create
9
+ return self.new(dbm)
10
+ end
11
+
12
+ def initialize(org_database_migration)
13
+ @org_database_migration = org_database_migration
14
+ super()
15
+ end
16
+
17
+ def liquid_drops
18
+ return super.merge(
19
+ org_name: @org_database_migration.organization.name,
20
+ source_host: @org_database_migration.displaysafe_source_url,
21
+ destination_host: @org_database_migration.displaysafe_destination_url,
22
+ )
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/message/template"
4
+
5
+ module Webhookdb::Messages::Testers
6
+ class Base < Webhookdb::Message::Template
7
+ def template_folder
8
+ return "specs"
9
+ end
10
+
11
+ def layout
12
+ return nil
13
+ end
14
+ end
15
+
16
+ class Basic < Base
17
+ end
18
+
19
+ class WithField < Base
20
+ def initialize(field)
21
+ @field = field
22
+ super()
23
+ end
24
+
25
+ def liquid_drops
26
+ return super.merge(field: @field)
27
+ end
28
+ end
29
+
30
+ class Nonextant < Base
31
+ end
32
+
33
+ class MissingField < Base
34
+ def template_name
35
+ return "with_field"
36
+ end
37
+ end
38
+
39
+ class WithInclude < Base
40
+ def liquid_drops
41
+ return super.merge(field: 3)
42
+ end
43
+ end
44
+
45
+ class WithPartial < Base
46
+ end
47
+
48
+ class WithLayout < Base
49
+ def template_name
50
+ return "basic"
51
+ end
52
+
53
+ def layout
54
+ return "standard"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/message/template"
4
+
5
+ class Webhookdb::Messages::Verification < Webhookdb::Message::Template
6
+ def self.fixtured(recipient)
7
+ code = Webhookdb::Fixtures.reset_code(customer: recipient).create
8
+ return self.new(code)
9
+ end
10
+
11
+ def initialize(reset_code)
12
+ @reset_code = reset_code
13
+ super()
14
+ end
15
+
16
+ def liquid_drops
17
+ return super.merge(
18
+ expire_at: @reset_code.expire_at,
19
+ token: @reset_code.token,
20
+ email: @reset_code.customer.email,
21
+ )
22
+ end
23
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb" unless defined?(Webhookdb)
4
+
5
+ # A collection of methods for declaring other methods.
6
+ #
7
+ # class MyClass
8
+ # extend Webhookdb::MethodUtilities
9
+ #
10
+ # singleton_attr_accessor :types
11
+ # singleton_method_alias :kinds, :types
12
+ # end
13
+ #
14
+ # MyClass.types = [ :pheno, :proto, :stereo ]
15
+ # MyClass.kinds # => [:pheno, :proto, :stereo]
16
+ #
17
+ module Webhookdb::MethodUtilities
18
+ ### Creates instance variables and corresponding methods that return their
19
+ ### values for each of the specified +symbols+ in the singleton of the
20
+ ### declaring object (e.g., class instance variables and methods if declared
21
+ ### in a Class).
22
+ def singleton_attr_reader(*symbols)
23
+ singleton_class.instance_exec(symbols) do |attrs|
24
+ attr_reader(*attrs)
25
+ end
26
+ end
27
+
28
+ ### Create instance variables and corresponding methods that return
29
+ ### true or false values for each of the specified +symbols+ in the singleton
30
+ ### of the declaring object.
31
+ def singleton_predicate_reader(*symbols)
32
+ singleton_class.extend(Webhookdb::MethodUtilities)
33
+ singleton_class.attr_predicate(*symbols)
34
+ end
35
+
36
+ ### Creates methods that allow assignment to the attributes of the singleton
37
+ ### of the declaring object that correspond to the specified +symbols+.
38
+ def singleton_attr_writer(*symbols)
39
+ singleton_class.instance_exec(symbols) do |attrs|
40
+ attr_writer(*attrs)
41
+ end
42
+ end
43
+
44
+ ### Creates readers and writers that allow assignment to the attributes of
45
+ ### the singleton of the declaring object that correspond to the specified
46
+ ### +symbols+.
47
+ def singleton_attr_accessor(*symbols)
48
+ symbols.each do |sym|
49
+ singleton_class.__send__(:attr_accessor, sym)
50
+ end
51
+ end
52
+
53
+ ### Create predicate methods and writers that allow assignment to the attributes
54
+ ### of the singleton of the declaring object that correspond to the specified
55
+ ### +symbols+.
56
+ def singleton_predicate_accessor(*symbols)
57
+ singleton_class.extend(Webhookdb::MethodUtilities)
58
+ singleton_class.attr_predicate_accessor(*symbols)
59
+ end
60
+
61
+ ### Creates an alias for the +original+ method named +newname+.
62
+ def singleton_method_alias(newname, original)
63
+ singleton_class.__send__(:alias_method, newname, original)
64
+ end
65
+
66
+ ### Create a reader in the form of a predicate for the given +attrname+.
67
+ def attr_predicate(attrname)
68
+ attrname = attrname.to_s.chomp("?")
69
+ define_method(:"#{attrname}?") do
70
+ instance_variable_get(:"@#{attrname}") ? true : false
71
+ end
72
+ end
73
+
74
+ ### Create a reader in the form of a predicate for the given +attrname+
75
+ ### as well as a regular writer method.
76
+ def attr_predicate_accessor(attrname)
77
+ attrname = attrname.to_s.chomp("?")
78
+ attr_writer(attrname)
79
+
80
+ attr_predicate(attrname)
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appydays/configurable"
4
+
5
+ module Webhookdb::MicrosoftCalendar
6
+ include Appydays::Configurable
7
+ extend Webhookdb::MethodUtilities
8
+
9
+ singleton_attr_accessor :calendar_view_start_time
10
+ singleton_attr_accessor :calendar_view_end_time
11
+
12
+ configurable(:microsoft_calendar) do
13
+ # How many calendars/events should we fetch in a single page?
14
+ # Higher uses slightly more memory but fewer API calls.
15
+ # Apparent maximum is 999,999,999.
16
+ setting :list_page_size, 500
17
+ # How many rows should we upsert at a time?
18
+ # Higher is fewer upserts, but can create very large SQL strings,
19
+ # which can have negative performance.
20
+ setting :upsert_page_size, 500
21
+
22
+ # These should be ISO8601 strings. We use them in our calls to the Microsoft Graph API
23
+ # to determine the timeframe we are pulling events from.
24
+ setting :calendar_view_start, Time.new(2022, 10, 1).iso8601
25
+ setting :calendar_view_end, (Time.new(2022, 10, 1) + 1825.days).iso8601
26
+
27
+ setting :http_timeout, 30
28
+
29
+ after_configured do
30
+ self.calendar_view_start_time = Time.parse(self.calendar_view_start)
31
+ self.calendar_view_end_time = Time.parse(self.calendar_view_end)
32
+ raise ArgumentError, "calendar view end must be after calendar view start" if
33
+ self.calendar_view_start_time >= self.calendar_view_end_time
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appydays/configurable"
4
+
5
+ module Webhookdb::Nextpax
6
+ include Appydays::Configurable
7
+
8
+ configurable(:nextpax) do
9
+ setting :constants_sync_cron_expression, "0 */12 * * *"
10
+ setting :property_changes_cron_expression, "*/1 * * * *"
11
+ setting :http_timeout, 30
12
+ setting :page_size, 20
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/front"
4
+
5
+ class Webhookdb::Oauth::Front < Webhookdb::Oauth::Provider
6
+ include Appydays::Loggable
7
+
8
+ def key = "front"
9
+ def app_name = "Front"
10
+ def requires_webhookdb_auth? = true
11
+ def supports_webhooks? = true
12
+
13
+ def authorization_url(state:)
14
+ return "https://app.frontapp.com/oauth/authorize?response_type=code&redirect_uri=#{Webhookdb::Front.oauth_callback_url}&state=#{state}&client_id=#{Webhookdb::Front.client_id}"
15
+ end
16
+
17
+ def exchange_authorization_code(code:)
18
+ token = Webhookdb::Http.post(
19
+ "https://app.frontapp.com/oauth/token",
20
+ {
21
+ "code" => code,
22
+ "redirect_uri" => Webhookdb::Front.oauth_callback_url,
23
+ "grant_type" => "authorization_code",
24
+ },
25
+ logger: self.logger,
26
+ timeout: Webhookdb::Front.http_timeout,
27
+ basic_auth: {username: Webhookdb::Front.client_id, password: Webhookdb::Front.client_secret},
28
+ )
29
+ return Webhookdb::Oauth::Tokens.new(
30
+ access_token: token.parsed_response["access_token"],
31
+ refresh_token: token.parsed_response["refresh_token"],
32
+ )
33
+ end
34
+
35
+ def build_marketplace_integrations(organization:, tokens:, **)
36
+ # I asked the dev team at front specifically how to differentiate between instances when receiving webhooks,
37
+ # and they said to look at the root url of the link provided for the resource in every response. In order to
38
+ # retrieve that value for the integrations that we'll be finding or creating, we look at this token info
39
+ # response.
40
+ front_token_info_resp = Webhookdb::Http.get(
41
+ "https://api2.frontapp.com/me",
42
+ headers: Webhookdb::Front.auth_headers(tokens.access_token),
43
+ logger: self.logger,
44
+ timeout: Webhookdb::Front.http_timeout,
45
+ )
46
+ front_token_info = front_token_info_resp.parsed_response
47
+ resource_url = front_token_info.dig("_links", "self")
48
+ instance_root_url = resource_url.nil? ? nil : URI.parse(resource_url).host
49
+
50
+ root_sint = Webhookdb::ServiceIntegration.create_disambiguated(
51
+ "front_marketplace_root_v1",
52
+ organization:,
53
+ api_url: instance_root_url,
54
+ backfill_key: tokens.refresh_token,
55
+ )
56
+ root_sint.replicator.build_dependents
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/intercom"
4
+
5
+ class Webhookdb::Oauth::Intercom < Webhookdb::Oauth::Provider
6
+ include Appydays::Loggable
7
+
8
+ def key = "intercom"
9
+ def app_name = "Intercom"
10
+ def requires_webhookdb_auth? = false
11
+ def supports_webhooks? = false
12
+
13
+ def authorization_url(state:)
14
+ return "https://app.intercom.com/oauth?client_id=#{Webhookdb::Intercom.client_id}&state=#{state}"
15
+ end
16
+
17
+ def exchange_authorization_code(code:)
18
+ token_resp = Webhookdb::Http.post(
19
+ "https://api.intercom.io/auth/eagle/token",
20
+ {
21
+ "client_id" => Webhookdb::Intercom.client_id,
22
+ "client_secret" => Webhookdb::Intercom.client_secret,
23
+ "code" => code,
24
+ },
25
+ logger: self.logger,
26
+ timeout: Webhookdb::Intercom.http_timeout,
27
+ )
28
+ return Webhookdb::Oauth::Tokens.new(access_token: token_resp.parsed_response["token"])
29
+ end
30
+
31
+ def find_or_create_customer(tokens:, scope:)
32
+ intercom_user_resp = Webhookdb::Http.get(
33
+ "https://api.intercom.io/me",
34
+ headers: Webhookdb::Intercom.auth_headers(tokens.access_token),
35
+ logger: self.logger,
36
+ timeout: Webhookdb::Intercom.http_timeout,
37
+ )
38
+
39
+ intercom_user = intercom_user_resp.parsed_response
40
+ scope[:me_response] = intercom_user
41
+ intercom_email = intercom_user.fetch("email")
42
+ return Webhookdb::Customer.find_or_create_for_email(intercom_email)
43
+ end
44
+
45
+ def build_marketplace_integrations(organization:, tokens:, scope:)
46
+ intercom_user = scope.fetch(:me_response)
47
+ # The intercom workspace id is used in the intercom webhook endpoint to identify which
48
+ # service integration to delegate requests to.
49
+ intercom_workspace_id = intercom_user.dig("app", "id_code")
50
+ root_sint = Webhookdb::ServiceIntegration.create_disambiguated(
51
+ "intercom_marketplace_root_v1",
52
+ organization:,
53
+ api_url: intercom_workspace_id,
54
+ backfill_key: tokens.access_token,
55
+ )
56
+ root_sint.replicator.build_dependents
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/postgres"
4
+ require "webhookdb/oauth"
5
+
6
+ class Webhookdb::Oauth::Session < Webhookdb::Postgres::Model(:oauth_sessions)
7
+ plugin :timestamps
8
+
9
+ many_to_one :customer, class: "Webhookdb::Customer"
10
+ many_to_one :organization, class: "Webhookdb::Organization"
11
+
12
+ dataset_module do
13
+ def usable
14
+ return self.where(used_at: nil).where { created_at > 30.minutes.ago }
15
+ end
16
+ end
17
+
18
+ def self.params_for_request(request)
19
+ return {
20
+ peer_ip: request.ip,
21
+ user_agent: request.user_agent || "(unset)",
22
+ }
23
+ end
24
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Webhookdb::Oauth
4
+ class Tokens < Webhookdb::TypedStruct
5
+ attr_reader :access_token, :refresh_token
6
+
7
+ def initialize(**kwargs)
8
+ super
9
+ self.typecheck!(:access_token, String)
10
+ self.typecheck!(:refresh_token, String, nullable: true)
11
+ end
12
+ end
13
+
14
+ # rubocop:disable Lint/UnusedMethodArgument
15
+ class Provider
16
+ # @return [String] Unique key to identify the provider.
17
+ def key = raise NotImplementedError
18
+
19
+ # @return [String] Name of the app to present to users.
20
+ def app_name = raise NotImplementedError
21
+
22
+ # True if auth with this provider requires the user auth in WebhookDB,
23
+ # false if we can get their email from the Oauth process.
24
+ # If the access token can be used to get the 'me' user,
25
+ # we can usually use their email for the customer,
26
+ # but this may not be possible for some integrations.
27
+ def requires_webhookdb_auth? = raise NotImplementedError
28
+
29
+ # This is similar to `supports_webhooks` in the Replicator descriptors,
30
+ # except that this is used to make the success page dynamic.
31
+ # True if this provider's integrations support webhooks
32
+ # (real-time or user-built webhook payloads).
33
+ def supports_webhooks? = raise NotImplementedError
34
+
35
+ # @return [String] The Oauth URL to send users to to begin OAuth.
36
+ def authorization_url(state:) = raise NotImplementedError
37
+
38
+ # Exchange the access code (from the authorization url) for access and/or refresh tokens.
39
+ # @return [Webhookdb::Oauth::Tokens]
40
+ def exchange_authorization_code(code:) = raise NotImplementedError
41
+
42
+ # @param tokens [Webhookdb::Oauth::Tokens]
43
+ # @param scope [Hash] Used to store data needed in later calls, like when building integrations.
44
+ # @return [Array{TrueClass, FalseClass, Webhookdb::Customer}]
45
+ def find_or_create_customer(tokens:, scope:)
46
+ raise RuntimeError("should not be called") if self.requires_webhookdb_auth?
47
+ raise NotImplementedError
48
+ end
49
+
50
+ # Create the actual service integrations for the given org.
51
+ # @param organization [Webhookdb::Organization]
52
+ # @param tokens [Webhookdb::Oauth::Tokens]
53
+ # # @param scope [Hash]
54
+ def build_marketplace_integrations(organization:, tokens:, scope:) = raise NotImplementedError
55
+ end
56
+ # rubocop:enable Lint/UnusedMethodArgument
57
+
58
+ class << self
59
+ # @return [String, Class]
60
+ def register(key, cls)
61
+ raise "#{key} already registered to #{cls}" if self.registry.include?(key)
62
+ self.registry[key] = cls
63
+ end
64
+
65
+ # @return [Provider]
66
+ def provider(key)
67
+ return self.registry.fetch(key).new
68
+ end
69
+
70
+ # @return [Hash]
71
+ def registry
72
+ return @registry ||= {}
73
+ end
74
+ end
75
+ end
76
+
77
+ require "webhookdb/oauth/front"
78
+ Webhookdb::Oauth.register(Webhookdb::Oauth::Front.new.key, Webhookdb::Oauth::Front)
79
+ require "webhookdb/oauth/intercom"
80
+ Webhookdb::Oauth.register(Webhookdb::Oauth::Intercom.new.key, Webhookdb::Oauth::Intercom)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appydays/configurable"
4
+ require "appydays/loggable"
5
+
6
+ class Webhookdb::Organization::Alerting
7
+ include Appydays::Configurable
8
+
9
+ configurable(:alerting) do
10
+ setting :interval, 24.hours.to_i
11
+ end
12
+
13
+ attr_reader :org
14
+
15
+ def initialize(org)
16
+ @org = org
17
+ end
18
+
19
+ # Dispatch the message template to administrators of the org.
20
+ # @param message_template [Webhookdb::Message::Template]
21
+ def dispatch_alert(message_template)
22
+ unless message_template.respond_to?(:signature)
23
+ raise Webhookdb::InvalidPrecondition,
24
+ "message template #{message_template.template_name} must define a #signature method, " \
25
+ "which is a unique identity for this error type, used for grouping and idempotency"
26
+ end
27
+ signature = message_template.signature
28
+ self.org.admin_customers.each do |c|
29
+ idemkey = "orgalert-#{signature}-#{c.id}"
30
+ Webhookdb::Idempotency.every(Webhookdb::Organization::Alerting.interval).under_key(idemkey) do
31
+ message_template.dispatch_email(c)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Organization::DatabaseMigration < Webhookdb::Postgres::Model(:organization_database_migrations)
4
+ include Webhookdb::Dbutil
5
+
6
+ class MigrationInProgress < Webhookdb::DatabaseLocked; end
7
+ class MigrationAlreadyFinished < StandardError; end
8
+
9
+ plugin :timestamps
10
+ plugin :column_encryption do |enc|
11
+ enc.column :source_admin_connection_url
12
+ enc.column :destination_admin_connection_url
13
+ end
14
+
15
+ many_to_one :started_by, class: "Webhookdb::Customer"
16
+ many_to_one :organization, class: "Webhookdb::Organization"
17
+
18
+ dataset_module do
19
+ def ongoing
20
+ return self.where(finished_at: nil)
21
+ end
22
+ end
23
+
24
+ def self.guard_ongoing!(org)
25
+ dbm = self.where(organization: org).ongoing.first
26
+ return if dbm.nil?
27
+ raise MigrationInProgress, "Organization #{org.name} has an ongoing database host migration so " \
28
+ "cannot be modified. We'll let admins know when it's done. Try again soon."
29
+ end
30
+
31
+ def self.enqueue(admin_connection_url_raw:, readonly_connection_url_raw:, public_host:, started_by:, organization:)
32
+ self.guard_ongoing!(organization)
33
+ self.db.transaction do
34
+ dbm = self.create(
35
+ started_by:,
36
+ organization:,
37
+ organization_schema: organization.replication_schema,
38
+ source_admin_connection_url: organization.admin_connection_url_raw,
39
+ destination_admin_connection_url: admin_connection_url_raw,
40
+ )
41
+ organization.update(
42
+ public_host:,
43
+ admin_connection_url_raw:,
44
+ readonly_connection_url_raw:,
45
+ )
46
+ return dbm
47
+ end
48
+ end
49
+
50
+ def displaysafe_source_url
51
+ return displaysafe_url(self.source_admin_connection_url)
52
+ end
53
+
54
+ def displaysafe_destination_url
55
+ return displaysafe_url(self.destination_admin_connection_url)
56
+ end
57
+
58
+ def status
59
+ return "finished" if self.finished_at.present?
60
+ return "in_progress" if self.started_at.present?
61
+ return "enqueued"
62
+ end
63
+
64
+ def finished?
65
+ return !!self.finished_at
66
+ end
67
+
68
+ def migrate
69
+ raise MigrationAlreadyFinished if self.finished?
70
+ self.update(started_at: Time.now) if self.started_at.nil?
71
+ borrow_conn(self.source_admin_connection_url) do |srcdb|
72
+ borrow_conn(self.destination_admin_connection_url) do |dstdb|
73
+ self.organization.service_integrations.sort_by(&:id).each do |sint|
74
+ next if sint.id <= self.last_migrated_service_integration_id
75
+ self.migrate_service_integration(sint, srcdb, dstdb)
76
+ self.update(last_migrated_service_integration_id: sint.id, last_migrated_timestamp: nil)
77
+ end
78
+ end
79
+ end
80
+ self.update(finished_at: Time.now)
81
+ end
82
+
83
+ # @param [Webhookdb::ServiceIntegration] service_integration
84
+ protected def migrate_service_integration(service_integration, srcdb, dstdb)
85
+ svc = service_integration.replicator
86
+ # If the service integration was not synced in the old db, skip it
87
+ return unless srcdb.table_exists?(svc.qualified_table_sequel_identifier)
88
+ svc.create_table_modification(if_not_exists: true).execute(dstdb)
89
+ ds = srcdb[svc.qualified_table_sequel_identifier].order(svc.timestamp_column.name)
90
+ (ds = ds.where(Sequel[svc.timestamp_column.name] > self.last_migrated_timestamp)) unless
91
+ self.last_migrated_timestamp.nil?
92
+ chunksize = Webhookdb::Organization.database_migration_page_size
93
+ chunk = []
94
+ ds.paged_each(rows_per_fetch: chunksize, hold: true, cursor_name: "whdb_dbmigration_#{self.id}") do |row|
95
+ chunk << row
96
+ if chunk.size >= chunksize
97
+ self.upsert_chunk(service_integration, dstdb, chunk)
98
+ chunk.clear
99
+ Amigo::DurableJob.heartbeat
100
+ end
101
+ end
102
+ self.upsert_chunk(service_integration, dstdb, chunk)
103
+ end
104
+
105
+ # @param [Webhookdb::ServiceIntegration] service_integration
106
+ protected def upsert_chunk(service_integration, dstdb, chunk)
107
+ return if chunk.empty?
108
+ svc = service_integration.replicator
109
+ chunk.each { |h| h.delete(svc.primary_key_column.name) }
110
+ tscol = svc.timestamp_column.name
111
+ dstdb[svc.qualified_table_sequel_identifier].
112
+ insert_conflict(
113
+ target: svc.remote_key_column.name,
114
+ update_where: svc._update_where_expr,
115
+ ).multi_insert(chunk)
116
+ self.update(last_migrated_timestamp: chunk.last[tscol])
117
+ end
118
+
119
+ def finish(now: Time.now)
120
+ self.update(
121
+ finished_at: now,
122
+ source_admin_connection_url: displaysafe_source_url,
123
+ destination_admin_connection_url: displaysafe_destination_url,
124
+ )
125
+ return self
126
+ end
127
+ end
128
+
129
+ # Table: organization_database_migrations
130
+ # -------------------------------------------------------------------------------------------------------------------------
131
+ # Columns:
132
+ # id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
133
+ # created_at | timestamp with time zone | NOT NULL DEFAULT now()
134
+ # updated_at | timestamp with time zone |
135
+ # started_at | timestamp with time zone |
136
+ # finished_at | timestamp with time zone |
137
+ # organization_id | integer | NOT NULL
138
+ # started_by_id | integer |
139
+ # source_admin_connection_url | text |
140
+ # destination_admin_connection_url | text |
141
+ # organization_schema | text |
142
+ # last_migrated_service_integration_id | integer | NOT NULL DEFAULT 0
143
+ # last_migrated_timestamp | timestamp with time zone |
144
+ # Indexes:
145
+ # organization_database_migrations_pkey | PRIMARY KEY btree (id)
146
+ # one_inprogress_migration_per_org | UNIQUE btree (organization_id) WHERE finished_at IS NULL
147
+ # organization_database_migrations_organization_id_index | btree (organization_id)
148
+ # Foreign key constraints:
149
+ # organization_database_migrations_organization_id_fkey | (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
150
+ # organization_database_migrations_started_by_id_fkey | (started_by_id) REFERENCES customers(id) ON DELETE SET NULL
151
+ # -------------------------------------------------------------------------------------------------------------------------