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,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ require "webhookdb/db_adapter/default_sql"
6
+
7
+ class Webhookdb::DBAdapter::PG < Webhookdb::DBAdapter
8
+ include Webhookdb::DBAdapter::ColumnTypes
9
+ include Webhookdb::DBAdapter::DefaultSql
10
+
11
+ VERIFY_TIMEOUT = 2
12
+ VERIFY_STATEMENT = "SELECT 1"
13
+
14
+ def identifier_quote_char
15
+ return '"'
16
+ end
17
+
18
+ def create_index_sql(index, concurrently:)
19
+ tgts = index.targets.map { |c| self.escape_identifier(c.name) }.join(", ")
20
+ uniq = index.unique ? " UNIQUE" : ""
21
+ concurrent = concurrently ? " CONCURRENTLY" : ""
22
+ idxname = self.escape_identifier(index.name)
23
+ tblname = self.qualify_table(index.table)
24
+ where = ""
25
+ where = " " + Webhookdb::Customer.where(index.where).sql.delete_prefix('SELECT * FROM "customers" ') if index.where
26
+ return "CREATE#{uniq} INDEX#{concurrent} IF NOT EXISTS #{idxname} ON #{tblname} (#{tgts})#{where}"
27
+ end
28
+
29
+ def column_create_sql(column)
30
+ modifiers = +""
31
+ coltype = COLTYPE_MAP.fetch(column.type)
32
+ if column.pk?
33
+ coltype = "bigserial" if column.type == BIGINT
34
+ coltype = "serial" if column.type == INTEGER
35
+ modifiers << " PRIMARY KEY"
36
+ elsif column.unique?
37
+ modifiers << " UNIQUE NOT NULL"
38
+ elsif !column.nullable?
39
+ modifiers << " NOT NULL"
40
+ end
41
+ colname = self.escape_identifier(column.name)
42
+ return "#{colname} #{coltype}#{modifiers}"
43
+ end
44
+
45
+ def add_column_sql(table, column, if_not_exists: false)
46
+ c = self.column_create_sql(column)
47
+ ifne = if_not_exists ? " IF NOT EXISTS" : ""
48
+ return "ALTER TABLE #{self.qualify_table(table)} ADD COLUMN#{ifne} #{c}"
49
+ end
50
+
51
+ def merge_from_csv(connection, file, table, pk_col, copy_columns)
52
+ qtable = self.qualify_table(table)
53
+ temptable = "#{self.escape_identifier(table.name)}_staging_#{SecureRandom.hex(4)}"
54
+ connection.using do |db|
55
+ db << "CREATE TEMP TABLE #{temptable} (LIKE #{qtable})"
56
+ db.copy_into(temptable.to_sym, options: "DELIMITER ',', HEADER true, FORMAT csv", data: file)
57
+ pkname = self.escape_identifier(pk_col.name)
58
+ col_assigns = self.assign_columns_sql("src", nil, copy_columns)
59
+ upsert_sql = [
60
+ <<~UPDATE,
61
+ UPDATE #{qtable} AS tgt
62
+ SET #{col_assigns} FROM
63
+ (SELECT * FROM #{temptable} WHERE #{pkname} IN (SELECT #{pkname} FROM #{qtable})) src
64
+ WHERE tgt.#{pkname} = src.#{pkname};
65
+ UPDATE
66
+ "INSERT INTO #{qtable} SELECT * FROM #{temptable} WHERE #{pkname} NOT IN (SELECT #{pkname} FROM #{qtable});",
67
+ ]
68
+ db << upsert_sql.join("\n")
69
+ end
70
+ end
71
+
72
+ def verify_connection(url, timeout: 2, statement: "SELECT 1")
73
+ conn = self.connection(url)
74
+ conn.using(connect_timeout: timeout) do |c|
75
+ c.execute("SET statement_timeout TO #{timeout * 1000}")
76
+ c.execute(statement)
77
+ end
78
+ end
79
+
80
+ COLTYPE_MAP = {
81
+ BIGINT => "bigint",
82
+ BIGINT_ARRAY => "bigint[]",
83
+ BOOLEAN => "boolean",
84
+ DATE => "date",
85
+ DECIMAL => "numeric",
86
+ DOUBLE => "double precision",
87
+ FLOAT => "float",
88
+ INTEGER => "integer",
89
+ INTEGER_ARRAY => "integer[]",
90
+ OBJECT => "jsonb",
91
+ TEXT => "text",
92
+ TEXT_ARRAY => "text[]",
93
+ TIMESTAMP => "timestamptz",
94
+ UUID => "uuid",
95
+ }.freeze
96
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/db_adapter/default_sql"
4
+ require "webhookdb/snowflake"
5
+
6
+ class Webhookdb::DBAdapter::Snowflake < Webhookdb::DBAdapter
7
+ include Webhookdb::DBAdapter::ColumnTypes
8
+ include Webhookdb::DBAdapter::DefaultSql
9
+
10
+ class SnowsqlConnection < Webhookdb::DBAdapter::Connection
11
+ include Appydays::Loggable
12
+
13
+ def initialize(url)
14
+ super()
15
+ @url = url
16
+ end
17
+
18
+ def execute(sql, **)
19
+ self.logger.debug("snowflake_exec", statement: sql)
20
+ result = Webhookdb::Snowflake.run_cli(@url, sql, **)
21
+ self.logger.debug("snowflake_exec_result", result:)
22
+ return result
23
+ end
24
+ end
25
+
26
+ def connection(url)
27
+ return SnowsqlConnection.new(url)
28
+ end
29
+
30
+ def create_index_sql(*)
31
+ raise NotImplementedError, "Snowflake does not support indices"
32
+ end
33
+
34
+ def column_create_sql(column)
35
+ modifiers = +""
36
+ if column.unique?
37
+ modifiers << " UNIQUE NOT NULL"
38
+ elsif !column.nullable?
39
+ modifiers << " NOT NULL"
40
+ end
41
+ coltype = COLTYPE_MAP.fetch(column.type)
42
+ colname = self.escape_identifier(column.name)
43
+ return "#{colname} #{coltype}#{modifiers}"
44
+ end
45
+
46
+ def add_column_sql(table, column, if_not_exists: false)
47
+ c = self.column_create_sql(column)
48
+ # Snowflake has no 'ADD COLUMN IF NOT EXISTS' so we need to query the long way around
49
+ add_sql = "ALTER TABLE #{self.qualify_table(table)} ADD COLUMN #{c}"
50
+ return add_sql unless if_not_exists
51
+ # The 'ILIKE' is a case-insensitive string compare,
52
+ # which is important because snowflake uppercases values when it stores them.
53
+ conditional_sql = <<~SQL
54
+ EXECUTE IMMEDIATE $$
55
+ BEGIN
56
+ IF (NOT EXISTS(
57
+ SELECT * FROM INFORMATION_SCHEMA.COLUMNS
58
+ WHERE TABLE_SCHEMA ILIKE '#{table.schema.name}'
59
+ AND TABLE_NAME ILIKE '#{table.name}'
60
+ AND COLUMN_NAME ILIKE '#{column.name}'
61
+ )) THEN
62
+ #{add_sql};
63
+ END IF;
64
+ END;
65
+ $$
66
+ SQL
67
+ return conditional_sql.rstrip
68
+ end
69
+
70
+ def merge_from_csv(connection, file, table, pk_col, copy_columns)
71
+ raise Webhookdb::InvalidPrecondition, "table must have schema" if table.schema.nil?
72
+
73
+ qtable = self.qualify_table(table)
74
+
75
+ stage = self.escape_identifier("whdb_tempstage_#{SecureRandom.hex(2)}_#{table.name}")
76
+ stage = self.escape_identifier(table.schema.name) + "." + stage
77
+
78
+ pkname = self.escape_identifier(pk_col.name)
79
+ # JSON columns need to be parsed from the CSV, so object columns need parse_json calls.
80
+ col_assigns = self.assign_columns_sql("src", nil, copy_columns) do |c, lhs, rhs|
81
+ if c.type == OBJECT
82
+ [lhs, "parse_json(#{rhs})"]
83
+ else
84
+ [lhs, rhs]
85
+ end
86
+ end
87
+ col_names = copy_columns.map { |c| self.escape_identifier(c.name) }
88
+ col_values = col_names.each_with_index.map do |n, i|
89
+ if copy_columns[i].type == OBJECT
90
+ "parse_json(src.#{n})"
91
+ else
92
+ "src.#{n}"
93
+ end
94
+ end
95
+ col_placeholders = col_names.each_with_index.map { |n, i| "$#{i + 1} #{n}" }
96
+ # Props to https://stackoverflow.com/questions/63084511/snowflake-upsert-from-staged-files
97
+ # for the merge from stage code.
98
+ # The enclosed option is vital because otherwise it doesn't interpret JSON columns properly.
99
+ import_sql = <<~SQL
100
+ CREATE STAGE #{stage} FILE_FORMAT = (type = 'CSV' skip_header = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '"');
101
+
102
+ PUT file://#{file.path} @#{stage} auto_compress=true;
103
+
104
+ MERGE INTO #{qtable} AS tgt
105
+ USING (
106
+ SELECT #{col_placeholders.join(', ')} FROM @#{stage}
107
+ ) src
108
+ ON tgt.#{pkname} = src.#{pkname}
109
+ WHEN MATCHED THEN UPDATE SET #{col_assigns}
110
+ WHEN NOT MATCHED THEN INSERT (#{col_names.join(', ')}) values (#{col_values.join(', ')});
111
+ SQL
112
+ connection.execute(import_sql)
113
+ end
114
+
115
+ def _verify_connection(url, timeout:, statement:)
116
+ _ = timeout
117
+ conn = self.connection(url)
118
+ conn.execute(statement)
119
+ end
120
+
121
+ def identifier_quote_char
122
+ return ""
123
+ end
124
+
125
+ COLTYPE_MAP = {
126
+ BIGINT => "bigint",
127
+ BOOLEAN => "boolean",
128
+ DATE => "date",
129
+ DECIMAL => "numeric",
130
+ DOUBLE => "double precision",
131
+ FLOAT => "float",
132
+ INTEGER => "integer",
133
+ OBJECT => "object",
134
+ TEXT => "text",
135
+ TIMESTAMP => "timestamptz",
136
+ }.freeze
137
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::DBAdapter
4
+ require "webhookdb/db_adapter/column_types"
5
+
6
+ class UnsupportedAdapter < StandardError; end
7
+
8
+ VALID_IDENTIFIER = /^[a-zA-Z][a-zA-Z\d_ ]*$/
9
+ INVALID_IDENTIFIER_MESSAGE = "Identifiers must start with a letter and " \
10
+ "contain only letters, numbers, spaces, and underscores. " \
11
+ "See https://docs.webhookdb.com/concepts/valid-identifiers/ for rules " \
12
+ "about identifiers like schema, table, and column names."
13
+
14
+ class Schema < Webhookdb::TypedStruct
15
+ attr_reader :name
16
+
17
+ def initialize(**kwargs)
18
+ super
19
+ self.typecheck!(:name, Symbol)
20
+ end
21
+ end
22
+
23
+ class Table < Webhookdb::TypedStruct
24
+ attr_reader :name, :schema
25
+
26
+ def initialize(**kwargs)
27
+ super
28
+ self.typecheck!(:name, Symbol)
29
+ self.typecheck!(:schema, Schema, nullable: true)
30
+ end
31
+ end
32
+
33
+ class Column < Webhookdb::TypedStruct
34
+ include ColumnTypes
35
+ attr_reader :name, :type, :nullable, :unique, :index, :index_where, :pk, :backfill_statement, :backfill_expr
36
+ alias nullable? nullable
37
+ alias unique? unique
38
+ alias index? index
39
+ alias pk? pk
40
+
41
+ def initialize(**kwargs)
42
+ super
43
+ self.typecheck!(:name, Symbol)
44
+ self.typecheck!(:type, Symbol)
45
+ self.typecheck!(:nullable, :boolean)
46
+ self.typecheck!(:unique, :boolean)
47
+ self.typecheck!(:index, :boolean)
48
+ self.typecheck!(:pk, :boolean)
49
+ raise ArgumentError, "type #{self.type.inspect} is not known" unless COLUMN_TYPES.include?(self.type)
50
+ end
51
+
52
+ def _defaults
53
+ return {nullable: true, unique: false, index: false, pk: false}
54
+ end
55
+ end
56
+
57
+ class Index < Webhookdb::TypedStruct
58
+ attr_reader :name, :table, :targets, :unique, :where
59
+
60
+ def initialize(**kwargs)
61
+ super
62
+ self.typecheck!(:name, Symbol)
63
+ self.typecheck!(:table, Table)
64
+ self.typecheck!(:targets, Array)
65
+ self.typecheck!(:unique, :boolean)
66
+ end
67
+
68
+ def _defaults
69
+ return {unique: false}
70
+ end
71
+
72
+ # @!attribute name
73
+ # @return [Symbol]
74
+ # @!attribute table
75
+ # @return [Table]
76
+ # @!attribute targets
77
+ # @return [Array<Column>]
78
+ # @!attribute unique
79
+ # @return [Boolean]
80
+ end
81
+
82
+ class TableDescriptor < Webhookdb::TypedStruct
83
+ attr_reader :table, :columns, :indices
84
+
85
+ def initialize(**kwargs)
86
+ super
87
+ self.typecheck!(:table, Table)
88
+ self.typecheck!(:columns, Array)
89
+ self.typecheck!(:indices, Array)
90
+ end
91
+
92
+ # @!attribute table
93
+ # @return [Table]
94
+ # @!attribute columns
95
+ # @return [Array<Column>]
96
+ # @!attribute indices
97
+ # @return [Array<Index>]
98
+
99
+ def _defaults
100
+ return {indices: []}
101
+ end
102
+ end
103
+
104
+ # Abstract class representing a DB connection.
105
+ # Ususually this is a Sequel connection,
106
+ # but in could just be a stored URL (like for Snowflake
107
+ # we have to call snowsql each time).
108
+ class Connection
109
+ def execute(sql)
110
+ raise NotImplementedError
111
+ end
112
+ end
113
+
114
+ class SequelConnection < Connection
115
+ include Webhookdb::Dbutil
116
+
117
+ def initialize(url)
118
+ super()
119
+ @url = url
120
+ end
121
+
122
+ def using(**kw, &)
123
+ borrow_conn(@url, **kw, &)
124
+ end
125
+
126
+ def execute(sql, **kw)
127
+ borrow_conn(@url, **kw) do |db|
128
+ db << sql
129
+ end
130
+ end
131
+ end
132
+
133
+ # Return a new Connection for the adapter.
134
+ # By default, return a SequelConnection,
135
+ # but adapters not using Sequel will need their own type.
136
+ # @return [Connection]
137
+ def connection(url)
138
+ return SequelConnection.new(url)
139
+ end
140
+
141
+ # @param [Schema] schema
142
+ # @param [Boolean] if_not_exists
143
+ # @return [String]
144
+ def create_schema_sql(schema, if_not_exists: false)
145
+ raise NotImplementedError
146
+ end
147
+
148
+ # @param [Table] table
149
+ # @param [Array<Column>] columns
150
+ # @param [Schema] schema
151
+ # @param [Boolean] if_not_exists
152
+ # @return [String]
153
+ def create_table_sql(table, columns, schema: nil, if_not_exists: false)
154
+ raise NotImplementedError
155
+ end
156
+
157
+ # @param [Index] index
158
+ # @return [String]
159
+ def create_index_sql(index, concurrently:)
160
+ raise NotImplementedError
161
+ end
162
+
163
+ # @param [Table] table
164
+ # @param [Column] column
165
+ # @param [Boolean] if_not_exists
166
+ # @return [String]
167
+ def add_column_sql(table, column, if_not_exists: false)
168
+ raise NotImplementedError
169
+ end
170
+
171
+ # Given a table and a (temporary) file with CSV data,
172
+ # import it into the table. Usually this is a COPY INTO command.
173
+ # For PG it would read from stdin,
174
+ # for Snowflake it would have to stage the file.
175
+ # @param [Connection] connection
176
+ # @param [File] file
177
+ # @param [Table] table
178
+ # @param [Column] pk_col Use this to identifier the same row between source and destination.
179
+ # @param [Array<Column>] copy_columns All columns to copy.
180
+ # NOTE: This includes the pk column, since it should be copied, as we depend on it persisting.
181
+ def merge_from_csv(connection, file, table, pk_col, copy_columns)
182
+ raise NotImplementedError
183
+ end
184
+
185
+ def verify_connection(url, timeout: 2, statement: "SELECT 1")
186
+ return self._verify_connection(url, timeout:, statement:)
187
+ end
188
+
189
+ # @param [String] url
190
+ # @return [Webhookdb::DBAdapter]
191
+ def self.adapter(url)
192
+ case url
193
+ when /^postgres/
194
+ return Webhookdb::DBAdapter::PG.new
195
+ when /^snowflake/
196
+ return Webhookdb::DBAdapter::Snowflake.new
197
+ else
198
+ raise UnsupportedAdapter, "no adapter available for #{url}"
199
+ end
200
+ end
201
+
202
+ def self.supported_adapters_message
203
+ return "Postgres (postgres://), SnowflakeDB (snowflake://)"
204
+ end
205
+ end
206
+
207
+ require "webhookdb/db_adapter/pg"
208
+ require "webhookdb/db_adapter/snowflake"
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel"
4
+
5
+ # Mixin that provides helpers when dealing with databases and connections.
6
+ #
7
+ # Use borrow_conn to create a connection that is disconnected after the block runs.
8
+ # A block must be given.
9
+ # By default, this connection uses the Webhookdb logger,
10
+ # and uses test: false and keep_reference: false Sequel.connect options,
11
+ # since this is a quick-lived and self-managed connection.
12
+ #
13
+ # Use take_conn where you will take care of disconnecting the connection.
14
+ # Note you MUST take care to call `disconnect` at some point
15
+ # or connections will leak.
16
+ module Webhookdb::Dbutil
17
+ include Appydays::Configurable
18
+
19
+ # See http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html#label-General+connection+options
20
+ # for Sequel option details.
21
+ configurable(:dbutil) do
22
+ # The number of (Float) seconds that should be considered "slow" for a
23
+ # single query; queries that take longer than this amount of time will be logged
24
+ # at `warn` level.
25
+ setting :slow_query_seconds, 0.1
26
+
27
+ # Default this to whatever concurrency is appropriate for the process type.
28
+ # PROC_MODE is set in the initializers in the config dir.
29
+ setting :max_connections,
30
+ (if ENV["PROC_MODE"] == "sidekiq"
31
+ ENV.fetch("SIDEKIQ_CONCURRENCY", "10").to_i
32
+ elsif ENV["PROC_MIDE"] == "puma"
33
+ ENV.fetch("WEB_CONCURRENCY", "4").to_i
34
+ else
35
+ 4
36
+ end)
37
+ setting :pool_timeout, 10
38
+ # Set to 'disable' to work around segfault.
39
+ # See https://github.com/ged/ruby-pg/issues/538
40
+ setting :gssencmode, ""
41
+ end
42
+
43
+ # Needed when we need to work with a source.
44
+ MOCK_CONN = Sequel.connect("mock://")
45
+
46
+ module_function def borrow_conn(url, **opts, &block)
47
+ raise LocalJumpError, "borrow_conn requires a block" if block.nil?
48
+ opts = conn_opts(opts)
49
+ Sequel.connect(url, **opts, &block)
50
+ end
51
+
52
+ module_function def take_conn(url, **opts, &block)
53
+ raise LocalJumpError, "take_conn cannot use a block" unless block.nil?
54
+ opts = conn_opts(opts)
55
+ return Sequel.connect(url, **opts, &block)
56
+ end
57
+
58
+ private module_function def conn_opts(opts)
59
+ res = Webhookdb::Dbutil.configured_connection_options
60
+ res.merge!(opts)
61
+ res[:test] = false unless res.key?(:test)
62
+ res[:loggers] = [Webhookdb.logger] unless res.key?(:logger) || res.key?(:loggers)
63
+ res[:keep_reference] = false unless res.key?(:keep_reference)
64
+ return res
65
+ end
66
+
67
+ def self.configured_connection_options
68
+ res = {}
69
+ res[:sql_log_level] ||= :debug
70
+ res[:log_warn_duration] ||= Webhookdb::Dbutil.slow_query_seconds
71
+ res[:max_connections] ||= Webhookdb::Dbutil.max_connections
72
+ res[:pool_timeout] ||= Webhookdb::Dbutil.pool_timeout
73
+ res[:driver_options] = {}
74
+ (res[:driver_options][:gssencmode] = Webhookdb::Dbutil.gssencmode) if Webhookdb::Dbutil.gssencmode.present?
75
+ return res
76
+ end
77
+
78
+ module_function def displaysafe_url(url)
79
+ u = URI(url)
80
+ u.user = "***"
81
+ u.password = "***"
82
+ return u.to_s
83
+ end
84
+
85
+ module_function def reduce_expr(dataset, op_symbol, operands, method: :where)
86
+ return dataset if operands.blank?
87
+ present_ops = operands.select(&:present?)
88
+ return dataset if present_ops.empty?
89
+ full_op = present_ops.reduce(&op_symbol)
90
+ return dataset.send(method, full_op)
91
+ end
92
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Webhookdb::DemoMode
4
+ include Appydays::Configurable
5
+ include Appydays::Loggable
6
+
7
+ configurable(:demo_mode) do
8
+ setting :client_enabled, false
9
+ setting :customer_email, "demo@webhookdb.com"
10
+ setting :customer_org_key, "demo_org"
11
+ setting :demo_org_id, 0
12
+ setting :demo_data_api_host, "https://api.webhookdb.com"
13
+ setting :example_datasets_enabled, false
14
+ end
15
+
16
+ class << self
17
+ # Should requests to this server allow demo mode? Usually this is true when running
18
+ # in a demo context, like initial local development.
19
+ def client_enabled? = self.client_enabled
20
+ # Should requests to this server respond to API requests for demo data?
21
+ def server_enabled? = self.demo_org_id.positive?
22
+
23
+ # @return [Array<Webhookdb::OrganizationMembership, Webhookdb::Replicator::StateMachineStep, String>]
24
+ def handle_auth
25
+ raise Webhookdb::InvalidPrecondition unless self.client_enabled?
26
+ _, customer = Webhookdb::Customer.find_or_create_for_email(self.customer_email)
27
+ membership = self._ensure_membership(customer)
28
+ membership.organization.publish_deferred("syncdemodata", membership.organization_id)
29
+ step = Webhookdb::Replicator::StateMachineStep.new.completed
30
+ message = %(Hi there! This is a demo version of WebhookDB.
31
+
32
+ You have been logged in automatically.
33
+
34
+ Your WebhookDB organization has also been set up with replicators for some APIs, like GitHub.
35
+
36
+ Run `webhookdb db connection` to get your database connection string,
37
+ and see what data is available.
38
+
39
+ To set up a new replicator, run `webhookdb services list` to see what is available.
40
+
41
+ You can also head to `webhookdb.com/deploy-builder` to prepare an environment for a deployment
42
+ into your own environment, like AWS or Heroku.
43
+
44
+ Or check out https://webhookdb.com to sign up for WebhookDB Cloud so this is all managed for you.)
45
+ return membership, step, message
46
+ end
47
+
48
+ def _ensure_membership(customer)
49
+ org = Webhookdb::Organization.find_or_create(key: self.customer_org_key) do |o|
50
+ o.name = "Demo Org"
51
+ o.billing_email = customer.email
52
+ end
53
+ mem = customer.all_memberships_dataset[organization: org] || customer.add_membership(
54
+ organization: org, membership_role: Webhookdb::Role.admin_role, verified: true, is_default: true,
55
+ )
56
+ return mem
57
+ end
58
+
59
+ def build_demo_data
60
+ evar = "DEMO_MODE_DEMO_ORG_ID"
61
+ raise Webhookdb::InvalidPrecondition, "#{evar} not set" unless self.server_enabled?
62
+ org = Webhookdb::Organization[self.demo_org_id] or
63
+ raise Webhookdb::InvalidPrecondition, "#{evar} #{self.demo_org_id} does not exist"
64
+ demo_sints = org.service_integrations.select { |sint| sint.service_name.start_with?("github_") }
65
+ data = demo_sints.map do |sint|
66
+ rows_data = sint.replicator.readonly_dataset { |ds| ds.select_map(:data) }
67
+ {
68
+ service_name: sint.service_name,
69
+ rows_data:,
70
+ }
71
+ end
72
+ return {data:}
73
+ end
74
+
75
+ def sync_demo_data(org)
76
+ can_run = Webhookdb::DemoMode.client_enabled? ||
77
+ Webhookdb::DemoMode.example_datasets_enabled
78
+ return false unless can_run
79
+ resp = Webhookdb::Http.post("#{self.demo_data_api_host}/v1/demo/data", timeout: nil, logger: self.logger)
80
+ # First, create/migrate all service integrations from the demo server.
81
+ sints_and_datas = []
82
+ resp.parsed_response["data"].each do |h|
83
+ service_name = h.fetch("service_name")
84
+ table_name = "#{service_name}_demo"
85
+ sint = org.service_integrations.find { |si| si.table_name == table_name } ||
86
+ org.add_service_integration(service_name:, table_name:)
87
+ sints_and_datas << [sint, h.fetch("rows_data")]
88
+ end
89
+ org.migrate_replication_tables
90
+ # Now populate them with data.
91
+ sints_and_datas.each do |(sint, rows)|
92
+ repl = sint.replicator
93
+ rows.each do |row|
94
+ repl.upsert_webhook_body(row)
95
+ end
96
+ end
97
+ return true
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appydays/configurable"
4
+ require "webhookdb/slack"
5
+
6
+ # Decouples the need to alert from the way we want to handle alerts.
7
+ # This is for something in between purely technical alerts (error handling in Sentry)
8
+ # and ops/marketing alerts (which may post to Slack or whatever).
9
+ # Instead of having to rewrite many async jobs to not use Slack,
10
+ # we can just modiy the one job that handles developer alerts.
11
+ class Webhookdb::DeveloperAlert
12
+ include Appydays::Configurable
13
+
14
+ attr_accessor :subsystem, :emoji, :fallback, :fields
15
+
16
+ def initialize(subsystem:, emoji:, fallback:, fields:)
17
+ @subsystem = subsystem
18
+ @emoji = emoji
19
+ @fallback = fallback
20
+ @fields = fields
21
+ end
22
+
23
+ def as_json
24
+ return {
25
+ subsystem:,
26
+ emoji:,
27
+ fallback:,
28
+ fields:,
29
+ }
30
+ end
31
+
32
+ def emit
33
+ Amigo.publish("webhookdb.developeralert.emitted", self.as_json)
34
+ end
35
+
36
+ def handle
37
+ notifier = Webhookdb::Slack.new_notifier(
38
+ channel: "#webhookdb-notifications",
39
+ username: @subsystem,
40
+ icon_emoji: @emoji,
41
+ )
42
+ notifier.post(
43
+ attachments: [
44
+ {
45
+ fallback: @fallback,
46
+ fields: @fields,
47
+ },
48
+ ],
49
+ )
50
+ end
51
+ end