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,491 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel/advisory_lock"
4
+ require "sequel/database"
5
+
6
+ # Support exporting WebhookDB data into external services,
7
+ # such as another Postgres instance or data warehouse (Snowflake, etc).
8
+ #
9
+ # At a high level, the way sync targets work are:
10
+ # - User uses the CLI to register a sync target for a specific integration
11
+ # using a database connection string for a supported db (ie, postgres://).
12
+ # - They include a period (how often it is synced), and an optional schema and table
13
+ # (if not used, we'll use the default schema, and the service integration table name).
14
+ # - Every minute or so, we look for sync targets that are "due" and enqueue a sync for them.
15
+ # Customers can enqueue their own sync request; but it cannot run more than the
16
+ # minimum allowed sync time.
17
+ #
18
+ # For the sync logic itself, see +run_sync+.
19
+ #
20
+ class Webhookdb::SyncTarget < Webhookdb::Postgres::Model(:sync_targets)
21
+ include Appydays::Configurable
22
+ include Webhookdb::Dbutil
23
+
24
+ class Deleted < StandardError; end
25
+ class InvalidConnection < StandardError; end
26
+ class SyncInProgress < StandardError; end
27
+
28
+ # Advisory locks for sync targets use this as the first int, and the id as the second.
29
+ ADVISORY_LOCK_KEYSPACE = 2_000_000_000
30
+
31
+ HTTP_VERIFY_TIMEOUT = 3
32
+ DB_VERIFY_TIMEOUT = 2000
33
+ DB_VERIFY_STATEMENT = "SELECT 1"
34
+ RAND = Random.new
35
+
36
+ configurable(:sync_target) do
37
+ # Allow installs to set this much lower if they want a faster sync.
38
+ # On production we use 1 minute as a default since it's faster than the replication delay
39
+ # of similar services but not fast enough to discourage self-hosting (which can be immediate).
40
+ # We have 10 minutes here for test compatibility (API endpoints read and store this when they are built).
41
+ # Can be overridden per-organization.
42
+ setting :default_min_period_seconds, 10.minutes.to_i
43
+ setting :max_period_seconds, 24.hours.to_i
44
+ # How many items sent in each POST for http sync targets.
45
+ setting :default_page_size, 200
46
+ # Sync targets without an explicit schema set
47
+ # will add tables into this schema. We use public by default
48
+ # since it's convenient, but for tests, it could cause conflicts
49
+ # so something else is set instead.
50
+ setting :default_schema, "public"
51
+ # If we want to sync to a localhost url for development purposes,
52
+ # we must allow sync targets to use http urls. This should only
53
+ # be used internally, and never in production.
54
+ setting :allow_http, false
55
+
56
+ after_configured do
57
+ if Webhookdb::RACK_ENV == "test"
58
+ safename = ENV["USER"].gsub(/[^A-Za-z]/, "")
59
+ self.default_schema = "synctest_#{safename}"
60
+ end
61
+ end
62
+ end
63
+
64
+ def self.valid_period(beginval)
65
+ return beginval..self.max_period_seconds
66
+ end
67
+
68
+ def self.valid_period_for(org)
69
+ return self.valid_period(org.minimum_sync_seconds)
70
+ end
71
+
72
+ def self.default_valid_period
73
+ return self.valid_period(Webhookdb::SyncTarget.default_min_period_seconds)
74
+ end
75
+
76
+ plugin :timestamps
77
+ plugin :column_encryption do |enc|
78
+ enc.column :connection_url
79
+ end
80
+
81
+ # Eventually we will allow full sync of an org's data,
82
+ # but for now let's link to just a service integration
83
+ many_to_one :service_integration, class: Webhookdb::ServiceIntegration
84
+ many_to_one :created_by, class: Webhookdb::Customer
85
+
86
+ dataset_module do
87
+ def due_for_sync(as_of:)
88
+ never_synced = Sequel[last_synced_at: nil]
89
+ next_due_at = Sequel[:last_synced_at] + (Sequel.lit("INTERVAL '1 second'") * Sequel[:period_seconds])
90
+ due_before_now = next_due_at <= as_of
91
+ return self.where(never_synced | due_before_now)
92
+ end
93
+ end
94
+
95
+ def http?
96
+ url = URI(self.connection_url)
97
+ return true if ["http", "https"].include?(url.scheme)
98
+ return false
99
+ end
100
+
101
+ def db?
102
+ return !self.http?
103
+ end
104
+
105
+ def self.validate_db_url(s)
106
+ begin
107
+ url = URI(s)
108
+ rescue URI::InvalidURIError
109
+ return "That's not a valid URL."
110
+ end
111
+ protocols = ["postgres", "snowflake"]
112
+ unless protocols.include?(url.scheme)
113
+ protostr = protocols.join(", ")
114
+ # rubocop:disable Layout/LineLength
115
+ msg = "The '#{url.scheme}' protocol is not supported for database sync targets. Supported protocols are: #{protostr}."
116
+ # rubocop:enable Layout/LineLength
117
+ return msg
118
+ end
119
+ return nil
120
+ end
121
+
122
+ def self.validate_http_url(s)
123
+ begin
124
+ url = URI(s)
125
+ rescue URI::InvalidURIError
126
+ return "That's not a valid URL."
127
+ end
128
+ case url.scheme
129
+ when "https"
130
+ return nil if url.user.present? || url.password.present?
131
+ url.user = "user"
132
+ url.password = "pass"
133
+ return "https urls must include a Basic Auth username and/or password, like '#{url}'"
134
+ when "http"
135
+ # http does not require a username/pass since it's only for internal use.
136
+ return Webhookdb::SyncTarget.allow_http ? nil : "Url must be https, not http."
137
+ else
138
+ return "Must be an https url."
139
+ end
140
+ end
141
+
142
+ def self.verify_db_connection(url)
143
+ adapter = Webhookdb::DBAdapter.adapter(url)
144
+ begin
145
+ adapter.verify_connection(url, timeout: DB_VERIFY_TIMEOUT, statement: DB_VERIFY_STATEMENT)
146
+ rescue StandardError => e
147
+ # noinspection RailsParamDefResolve
148
+ msg = e.try(:wrapped_exception).try(:to_s) || e.to_s
149
+ raise InvalidConnection, "Could not SELECT 1: #{msg.strip}"
150
+ end
151
+ end
152
+
153
+ def self.verify_http_connection(url)
154
+ cleanurl, authparams = Webhookdb::Http.extract_url_auth(url)
155
+ body = {
156
+ rows: [],
157
+ integration_id: "svi_test",
158
+ integration_service: "httpsync_test",
159
+ table: "test",
160
+ }
161
+ begin
162
+ Webhookdb::Http.post(
163
+ cleanurl,
164
+ body,
165
+ logger: self.logger,
166
+ basic_auth: authparams,
167
+ timeout: HTTP_VERIFY_TIMEOUT,
168
+ follow_redirects: true,
169
+ )
170
+ rescue Timeout::Error => e
171
+ raise InvalidConnection, "POST to #{cleanurl} timed out: #{e.message}"
172
+ rescue Webhookdb::Http::Error => e
173
+ raise InvalidConnection, "POST to #{cleanurl} failed: #{e.message}"
174
+ end
175
+ end
176
+
177
+ def next_scheduled_sync(now:)
178
+ return self.next_sync(self.period_seconds, now)
179
+ end
180
+
181
+ def next_possible_sync(now:)
182
+ return self.next_sync(self.organization.minimum_sync_seconds, now)
183
+ end
184
+
185
+ protected def next_sync(period, now)
186
+ return now if self.last_synced_at.nil?
187
+ return [now, self.last_synced_at + period].max
188
+ end
189
+
190
+ # Return the jitter used for enqueing the next sync of the job.
191
+ # It should never be more than 20 seconds,
192
+ # nor should it be more than 1/4 of the total period,
193
+ # since it needs to run at a reasonably predictable time.
194
+ # Jitter is always >= 1, since it is helpful to be able to assert it
195
+ # will always be in the future.
196
+ def jitter
197
+ max_jitter = [20, self.period_seconds / 4].min
198
+ max_jitter = [1, max_jitter].max
199
+ return RAND.rand(1..max_jitter)
200
+ end
201
+
202
+ # Running a sync involves some work we always do (export, transform),
203
+ # and then work that varies per-adapter (load).
204
+ #
205
+ # First, we lock using an advisory lock to make sure we never sync the same sync target
206
+ # concurrently. It can cause correctness and performance issues.
207
+ # Raise a +SyncInProgress+ error if we're currently syncing.
208
+ #
209
+ # If the sync target is against an HTTP URL, see +_run_http_sync+.
210
+ #
211
+ # If the sync target is a database connection:
212
+ #
213
+ # - Ensure the sync target table exists and has the right schema.
214
+ # In general we do NOT create indices for the target table;
215
+ # since this table is for a client's data warehouse, we assume they will optimize it as needed.
216
+ # The only exception is the unique constraint for the remote key column.
217
+ # - Select rows created/updated since our last update in our 'source' database.
218
+ # - Write them to disk into a CSV file.
219
+ # - Pass this CSV file to the proper sync target adapter.
220
+ # - For example, the PG sync target will:
221
+ # - Create a temp table in the target database, using the schema from the sync target table.
222
+ # - Load the data into that temp table.
223
+ # - Insert rows into the target table temp table rows that do not appear in the target table.
224
+ # - Update rows in the target table temp table rows that already appear in the target table.
225
+ # - The snowflake sync target will:
226
+ # - PUT the CSV file into the stage for the table.
227
+ # - Otherwise the logic is the same as PG: create a temp table and COPY INTO from the CSV.
228
+ # - Purge the staged file.
229
+ #
230
+ # @param now [Time] The current time. Rows that were updated <= to 'now', and >= the 'last updated' timestamp,
231
+ # will be synced.
232
+ def run_sync(now:)
233
+ ran = false
234
+ # Take the advisory lock with a separate connection. This seems to be pretty important-
235
+ # it's possible that (for reasons not clear at this time) using the standard connection pool
236
+ # results in the lock being held since the session remains open for a while on the worker.
237
+ # Opening a separate connection ensures that, once this method exits, the lock will be released
238
+ # since the session will be ended.
239
+ Webhookdb::Dbutil.borrow_conn(Webhookdb::Postgres::Model.uri) do |db|
240
+ self.advisory_lock(db).with_lock? do
241
+ routine = if self.connection_url.start_with?("https://", "http://")
242
+ # Note that http links are not secure and should only be used for development purposes
243
+ HttpRoutine.new(now, self)
244
+ else
245
+ DatabaseRoutine.new(now, self)
246
+ end
247
+ routine.run
248
+ ran = true
249
+ end
250
+ end
251
+ raise SyncInProgress, "SyncTarget[#{self.id}] is already being synced" unless ran
252
+ end
253
+
254
+ # @return [Sequel::AdvisoryLock]
255
+ def advisory_lock(db)
256
+ return Sequel::AdvisoryLock.new(db, ADVISORY_LOCK_KEYSPACE, self.id)
257
+ end
258
+
259
+ def displaysafe_connection_url
260
+ return displaysafe_url(self.connection_url)
261
+ end
262
+
263
+ # @return [String]
264
+ def associated_type
265
+ # Eventually we need to support orgs
266
+ return "service_integration"
267
+ end
268
+
269
+ # @return [String]
270
+ def associated_id
271
+ # Eventually we need to support orgs
272
+ return self.service_integration.opaque_id
273
+ end
274
+
275
+ def associated_object_display
276
+ return "#{self.service_integration.opaque_id}/#{self.service_integration.table_name}"
277
+ end
278
+
279
+ # @return [String]
280
+ def schema_and_table_string
281
+ schema_name = self.schema.present? ? self.schema : self.class.default_schema
282
+ table_name = self.table.present? ? self.table : self.service_integration.table_name
283
+ return "#{schema_name}.#{table_name}"
284
+ end
285
+
286
+ # @return [Webhookdb::Organization]
287
+ def organization
288
+ return self.service_integration.organization
289
+ end
290
+
291
+ def before_validation
292
+ self.page_size ||= Webhookdb::SyncTarget.default_page_size
293
+ super
294
+ end
295
+
296
+ def before_create
297
+ self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("syt")
298
+ end
299
+
300
+ # @!attribute service_integration
301
+ # @return [Webhookdb::ServiceIntegration]
302
+
303
+ # @!attribute connection_url
304
+ # @return [String]
305
+
306
+ # @!attribute last_synced_at
307
+ # @return [Time]
308
+
309
+ class Routine
310
+ attr_reader :now, :sync_target, :replicator, :timestamp_expr
311
+
312
+ def initialize(now, sync_target)
313
+ @now = now
314
+ @sync_target = sync_target
315
+ @last_synced_at = sync_target.last_synced_at
316
+ @replicator = sync_target.service_integration.replicator
317
+ @timestamp_expr = Sequel[@replicator.timestamp_column.name]
318
+ end
319
+
320
+ def run = raise NotImplementedError
321
+
322
+ # Get the dataset of rows that need to be synced.
323
+ # Note that there are a couple race conditions here.
324
+ # First, those in https://github.com/webhookdb/webhookdb/issues/571.
325
+ # There is also the condition that we could send the same row
326
+ # multiple times when the row timestamp is set to last_synced_at but
327
+ # it wasn't in the last sync; however that is likely not a big problem
328
+ # since clients need to handle updates in any case.
329
+ def dataset_to_sync
330
+ @replicator.readonly_dataset do |ds|
331
+ # Find rows updated before we started
332
+ tscond = (@timestamp_expr <= @now)
333
+ # Find rows updated after the last sync was run
334
+ @last_synced_at && (tscond &= (@timestamp_expr >= @last_synced_at))
335
+ ds = ds.where(tscond)
336
+ # We want to paginate from oldest to newest
337
+ ds = ds.order(@timestamp_expr)
338
+ yield(ds)
339
+ end
340
+ end
341
+
342
+ def record(last_synced_at)
343
+ self.sync_target.update(last_synced_at:)
344
+ rescue Sequel::NoExistingObject => e
345
+ raise Webhookdb::SyncTarget::Deleted, e
346
+ end
347
+ end
348
+
349
+ class HttpRoutine < Routine
350
+ def run
351
+ page_size = self.sync_target.page_size
352
+ self.dataset_to_sync do |ds|
353
+ chunk = []
354
+ ds.paged_each(rows_per_fetch: page_size) do |row|
355
+ chunk << row
356
+ self._flush_http_chunk(chunk) if chunk.size >= page_size
357
+ end
358
+ self._flush_http_chunk(chunk) unless chunk.empty?
359
+ # We should save 'now' as the timestamp, rather than the last updated row.
360
+ # This is important because other we'd keep trying to sync the last row synced.
361
+ self.record(self.now)
362
+ end
363
+ rescue Webhookdb::Http::Error, Errno::ECONNRESET, Net::ReadTimeout, Net::OpenTimeout, OpenSSL::SSL::SSLError => e
364
+ # This is handled well so no need to re-raise.
365
+ # We already committed the last page that was successful,
366
+ # so we can just stop syncing at this point to try again later.
367
+ self.sync_target.logger.warn("sync_target_http_error", error: e)
368
+ end
369
+
370
+ def _flush_http_chunk(chunk)
371
+ sint = self.sync_target.service_integration
372
+ body = {
373
+ rows: chunk,
374
+ integration_id: sint.opaque_id,
375
+ integration_service: sint.service_name,
376
+ table: sint.table_name,
377
+ sync_timestamp: self.now,
378
+ }
379
+ cleanurl, authparams = Webhookdb::Http.extract_url_auth(self.sync_target.connection_url)
380
+ Webhookdb::Http.post(
381
+ cleanurl,
382
+ body,
383
+ timeout: sint.organization.sync_target_timeout,
384
+ logger: self.sync_target.logger,
385
+ basic_auth: authparams,
386
+ )
387
+ latest_ts = chunk.last.fetch(self.replicator.timestamp_column.name)
388
+ # The client committed the sync page we sent. Record it in case of a future error,
389
+ # so we don't re-send the same page.
390
+ self.record(latest_ts)
391
+ chunk.clear
392
+ end
393
+ end
394
+
395
+ # - Ensure the sync target table exists and has the right schema.
396
+ # In general we do NOT create indices for the target table;
397
+ # since this table is for a client's data warehouse, we assume they will optimize it as needed.
398
+ # The only exception is the unique constraint for the remote key column.
399
+ # - Select rows created/updated since our last update in our 'source' database.
400
+ # - Write them to disk into a CSV file.
401
+ # - Pass this CSV file to the proper sync target adapter.
402
+ # - For example, the PG sync target will:
403
+ # - Create a temp table in the target database, using the schema from the sync target table.
404
+ # - Load the data into that temp table.
405
+ # - Insert rows into the target table temp table rows that do not appear in the target table.
406
+ # - Update rows in the target table temp table rows that already appear in the target table.
407
+ # - The snowflake sync target will:
408
+ # - PUT the CSV file into the stage for the table.
409
+ # - Otherwise the logic is the same as PG: create a temp table and COPY INTO from the CSV.
410
+ # - Purge the staged file.
411
+ #
412
+ class DatabaseRoutine < Routine
413
+ def initialize(now, sync_target)
414
+ super
415
+ @connection_url = self.sync_target.connection_url
416
+ @adapter = Webhookdb::DBAdapter.adapter(@connection_url)
417
+ @adapter_connection = @adapter.connection(@connection_url)
418
+ end
419
+
420
+ def run
421
+ schema_name = @sync_target.schema.present? ? @sync_target.schema : @sync_target.class.default_schema
422
+ table_name = @sync_target.table.present? ? @sync_target.table : @sync_target.service_integration.table_name
423
+ adapter = @adapter
424
+ schema = Webhookdb::DBAdapter::Schema.new(name: schema_name.to_sym)
425
+ table = Webhookdb::DBAdapter::Table.new(name: table_name.to_sym, schema:)
426
+
427
+ schema_lines = []
428
+ schema_lines << adapter.create_schema_sql(table.schema, if_not_exists: true)
429
+ schema_lines << adapter.create_table_sql(
430
+ table,
431
+ [@replicator.primary_key_column, @replicator.remote_key_column],
432
+ if_not_exists: true,
433
+ )
434
+ (@replicator.denormalized_columns + [@replicator.data_column]).each do |col|
435
+ schema_lines << adapter.add_column_sql(table, col, if_not_exists: true)
436
+ end
437
+ adapter_conn = adapter.connection(@connection_url)
438
+ schema_expr = schema_lines.join(";\n") + ";"
439
+ if schema_expr != self.sync_target.last_applied_schema
440
+ adapter_conn.execute(schema_expr)
441
+ self.sync_target.update(last_applied_schema: schema_expr)
442
+ end
443
+ tempfile = Tempfile.new("whdbsyncout-#{self.sync_target.id}")
444
+ begin
445
+ self.dataset_to_sync do |ds|
446
+ ds.db.copy_table(ds, options: "DELIMITER ',', HEADER true, FORMAT csv") do |row|
447
+ tempfile.write(row)
448
+ end
449
+ end
450
+ tempfile.rewind
451
+ adapter.merge_from_csv(
452
+ adapter_conn,
453
+ tempfile,
454
+ table,
455
+ @replicator.primary_key_column,
456
+ [@replicator.primary_key_column,
457
+ @replicator.remote_key_column,] + @replicator.denormalized_columns + [@replicator.data_column],
458
+ )
459
+ self.record(self.now)
460
+ ensure
461
+ tempfile.unlink
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ # Table: sync_targets
468
+ # --------------------------------------------------------------------------------------------------------------------------
469
+ # Columns:
470
+ # id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
471
+ # created_at | timestamp with time zone | NOT NULL DEFAULT now()
472
+ # updated_at | timestamp with time zone |
473
+ # opaque_id | text | NOT NULL
474
+ # service_integration_id | integer | NOT NULL
475
+ # created_by_id | integer |
476
+ # period_seconds | integer | NOT NULL
477
+ # connection_url | text | NOT NULL
478
+ # schema | text | NOT NULL DEFAULT ''::text
479
+ # table | text | NOT NULL DEFAULT ''::text
480
+ # last_synced_at | timestamp with time zone |
481
+ # last_applied_schema | text | NOT NULL DEFAULT ''::text
482
+ # page_size | integer | NOT NULL
483
+ # Indexes:
484
+ # sync_targets_pkey | PRIMARY KEY btree (id)
485
+ # sync_targets_opaque_id_key | UNIQUE btree (opaque_id)
486
+ # sync_targets_last_synced_at_index | btree (last_synced_at)
487
+ # sync_targets_service_integration_id_index | btree (service_integration_id)
488
+ # Foreign key constraints:
489
+ # sync_targets_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
490
+ # sync_targets_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id) ON DELETE CASCADE
491
+ # --------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/tasklib"
4
+
5
+ require "webhookdb"
6
+
7
+ module Webhookdb::Tasks
8
+ class Admin < Rake::TaskLib
9
+ def initialize
10
+ super()
11
+ namespace :admin do
12
+ desc "Add roles to the named org"
13
+ task :role, [:org_key, :role] do |_, args|
14
+ self.setup
15
+ org = self.find_org(args)
16
+ role = Webhookdb::Role.find_or_create(name: args.fetch(:role))
17
+ org.add_feature_role(role)
18
+ puts "Added role #{role.name} to #{org.name}"
19
+ end
20
+
21
+ task :backfill, [:org_key] do |_, args|
22
+ self.setup
23
+ org = self.find_org(args)
24
+ org.service_integrations.each do |sint|
25
+ Webhookdb::BackfillJob.create(service_integration: sint, incremental: false).enqueue
26
+ end
27
+ end
28
+
29
+ task :connection, [:org_key, :type] do |_, args|
30
+ self.setup
31
+ org = self.find_org(args)
32
+ type = args.fetch(:type, "readonly")
33
+ puts org.send(:"#{type}_connection_url")
34
+ end
35
+ end
36
+ end
37
+
38
+ def setup
39
+ Webhookdb.load_app
40
+ Webhookdb::Async.setup_web if Amigo.subscribers.empty?
41
+ end
42
+
43
+ def find_org(args)
44
+ org_key = args.fetch(:org_key)
45
+ org = Webhookdb::Organization[key: org_key] or raise "No org with key #{org_key}"
46
+ return org
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/tasklib"
4
+ require "sequel"
5
+
6
+ require "webhookdb"
7
+ require "webhookdb/postgres"
8
+
9
+ module Webhookdb::Tasks
10
+ class Annotate < Rake::TaskLib
11
+ def initialize
12
+ super()
13
+ desc "Update model annotations"
14
+ task :annotate do
15
+ unless `git diff`.blank?
16
+ puts "Cannot annotate while there is any git diff."
17
+ puts "Please commit or revert any diff and try again."
18
+ exit(1)
19
+ end
20
+
21
+ require "webhookdb"
22
+ Webhookdb.load_app
23
+ files = []
24
+ Webhookdb::Postgres.each_model_class do |cls|
25
+ files << "lib/#{cls.name.underscore}.rb" if cls.name
26
+ end
27
+
28
+ require "sequel/annotate"
29
+ Sequel::Annotate.annotate(files, border: true)
30
+ puts "Finished annotating:"
31
+ files.each { |f| puts " #{f}" }
32
+ puts "Please commit the changes."
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/tasklib"
4
+ require "sequel"
5
+
6
+ require "webhookdb"
7
+ require "webhookdb/postgres"
8
+
9
+ module Webhookdb::Tasks
10
+ class DB < Rake::TaskLib
11
+ def initialize
12
+ super()
13
+ namespace :db do
14
+ desc "Drop all tables in the public schema."
15
+ task :drop_tables do
16
+ require "webhookdb/postgres"
17
+ Webhookdb::Postgres.load_superclasses
18
+ Webhookdb::Postgres.each_model_superclass do |sc|
19
+ sc.db[:pg_tables].where(schemaname: "public").each do |tbl|
20
+ self.exec(sc.db, "DROP TABLE #{tbl[:tablename]} CASCADE")
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Remove all data from application schemas"
26
+ task :wipe do
27
+ require "webhookdb/postgres"
28
+ Webhookdb::Postgres.load_superclasses
29
+ Webhookdb::Postgres.each_model_class do |c|
30
+ c.truncate(cascade: true)
31
+ end
32
+ end
33
+
34
+ desc "Run migrations (rake db:migrate[<target>] to go to a specific version)"
35
+ task :migrate, [:version] do |_, args|
36
+ require "webhookdb/postgres"
37
+ Webhookdb::Postgres.load_superclasses
38
+ Webhookdb::Postgres.run_all_migrations(target: args[:version]&.to_i)
39
+ end
40
+
41
+ desc "Re-create the database tables. Drop tables and migrate."
42
+ task reset: ["db:drop_tables", "db:migrate"]
43
+
44
+ task :drop_replication_databases do
45
+ require "webhookdb/postgres"
46
+ Webhookdb::Postgres.load_superclasses
47
+ Webhookdb::Postgres.each_model_superclass do |c|
48
+ c.db[:pg_database].grep(:datname, "adb%").select(:datname).all.each do |row|
49
+ c.db << "DROP DATABASE #{row[:datname]}"
50
+ end
51
+ end
52
+ end
53
+
54
+ task drop_tables_and_replication_databases: ["db:drop_tables", "db:drop_replication_databases"]
55
+
56
+ task wipe_tables_and_drop_replication_databases: ["db:wipe", "db:drop_replication_databases"]
57
+
58
+ task :lookup_org_admin_url, [:org_id] do |_, args|
59
+ (orgid = args[:org_id]) or raise "Must provide org id as first argument"
60
+ require "webhookdb"
61
+ Webhookdb.load_app
62
+ org_cond = orgid.match?(/^\d$/) ? orgid.to_i : {key: orgid}
63
+ (org = Webhookdb::Organization[org_cond]) or raise "Org #{orgid} does not exist"
64
+ u = org.admin_connection_url
65
+ raise "Org #{orgid} has no connection url yet" if u.blank?
66
+ print(u)
67
+ end
68
+ end
69
+ end
70
+
71
+ def exec(db, cmd)
72
+ print cmd
73
+ begin
74
+ db.execute(cmd)
75
+ print "\n"
76
+ rescue StandardError
77
+ print " (error)\n"
78
+ raise
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/tasklib"
4
+ require "sequel"
5
+
6
+ require "webhookdb"
7
+ require "webhookdb/postgres"
8
+
9
+ module Webhookdb::Tasks
10
+ class Docs < Rake::TaskLib
11
+ def initialize
12
+ super()
13
+ namespace :docs do
14
+ desc "Write out auto-generated docs for integrations."
15
+ task :replicators, [:out, :name] do |_, args|
16
+ (out = args[:out]) or raise ArgumentError, "must pass :out param (directory to write files)"
17
+ require "webhookdb/replicator"
18
+ Webhookdb.load_app
19
+ if (rname = args[:name])
20
+ repl = Webhookdb::Replicator.registered!(rname)
21
+ puts self.replicator_md(repl)
22
+ else
23
+ descriptors = Webhookdb::Replicator::Docgen.documentable_descriptors
24
+ descriptors.each do |repl|
25
+ md = self.replicator_md(repl)
26
+ path = File.join(out, "#{repl.name}.md")
27
+ File.write(path, md)
28
+ end
29
+ list_md = Webhookdb::Replicator::Docgen.replicator_list_md(descriptors)
30
+ list_path = File.join(out, "../_includes/replicator_list.md")
31
+ File.write(list_path, list_md)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # @param desc [Webhookdb::Replicator::Descriptor]
38
+ def replicator_md(desc)
39
+ return Webhookdb::Replicator::Docgen.new(desc).markdown
40
+ end
41
+ end
42
+ end