webhookdb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (364) hide show
  1. checksums.yaml +7 -0
  2. data/data/messages/layouts/blank.email.liquid +10 -0
  3. data/data/messages/layouts/minimal.email.liquid +28 -0
  4. data/data/messages/layouts/standard.email.liquid +28 -0
  5. data/data/messages/partials/button.liquid +15 -0
  6. data/data/messages/partials/environment_banner.liquid +9 -0
  7. data/data/messages/partials/footer.liquid +22 -0
  8. data/data/messages/partials/greeting.liquid +3 -0
  9. data/data/messages/partials/logo_header.liquid +18 -0
  10. data/data/messages/partials/signoff.liquid +1 -0
  11. data/data/messages/styles/v1.liquid +346 -0
  12. data/data/messages/templates/errors/icalendar_fetch.email.liquid +29 -0
  13. data/data/messages/templates/invite.email.liquid +15 -0
  14. data/data/messages/templates/new_customer.email.liquid +24 -0
  15. data/data/messages/templates/org_database_migration_finished.email.liquid +7 -0
  16. data/data/messages/templates/org_database_migration_started.email.liquid +9 -0
  17. data/data/messages/templates/specs/_field_partial.liquid +1 -0
  18. data/data/messages/templates/specs/basic.email.liquid +2 -0
  19. data/data/messages/templates/specs/basic.fake.liquid +1 -0
  20. data/data/messages/templates/specs/with_field.email.liquid +2 -0
  21. data/data/messages/templates/specs/with_field.fake.liquid +1 -0
  22. data/data/messages/templates/specs/with_include.email.liquid +2 -0
  23. data/data/messages/templates/specs/with_partial.email.liquid +1 -0
  24. data/data/messages/templates/verification.email.liquid +14 -0
  25. data/data/messages/templates/verification.sms.liquid +1 -0
  26. data/data/messages/web/install-customer-login.liquid +48 -0
  27. data/data/messages/web/install-error.liquid +17 -0
  28. data/data/messages/web/install-success.liquid +35 -0
  29. data/data/messages/web/install.liquid +20 -0
  30. data/data/messages/web/partials/footer.liquid +4 -0
  31. data/data/messages/web/partials/form_error.liquid +1 -0
  32. data/data/messages/web/partials/header.liquid +3 -0
  33. data/data/messages/web/styles.liquid +134 -0
  34. data/data/windows_tz.txt +461 -0
  35. data/db/migrations/001_testing_pixies.rb +13 -0
  36. data/db/migrations/002_initial.rb +132 -0
  37. data/db/migrations/003_ux_overhaul.rb +20 -0
  38. data/db/migrations/004_incremental_backfill.rb +9 -0
  39. data/db/migrations/005_log_webhooks.rb +24 -0
  40. data/db/migrations/006_generalize_roles.rb +29 -0
  41. data/db/migrations/007_org_dns.rb +12 -0
  42. data/db/migrations/008_webhook_subscriptions.rb +19 -0
  43. data/db/migrations/009_nonunique_stripe_subscription_customer.rb +16 -0
  44. data/db/migrations/010_drop_integration_soft_delete.rb +14 -0
  45. data/db/migrations/011_webhook_subscriptions_created_at.rb +10 -0
  46. data/db/migrations/012_webhook_subscriptions_created_by.rb +9 -0
  47. data/db/migrations/013_default_org_membership.rb +30 -0
  48. data/db/migrations/014_webhook_subscription_deliveries.rb +26 -0
  49. data/db/migrations/015_dependent_integrations.rb +9 -0
  50. data/db/migrations/016_encrypted_columns.rb +9 -0
  51. data/db/migrations/017_skip_verification.rb +9 -0
  52. data/db/migrations/018_sync_targets.rb +25 -0
  53. data/db/migrations/019_org_schema.rb +9 -0
  54. data/db/migrations/020_org_database_migrations.rb +25 -0
  55. data/db/migrations/021_no_default_org_schema.rb +14 -0
  56. data/db/migrations/022_database_document.rb +15 -0
  57. data/db/migrations/023_sync_target_schema.rb +9 -0
  58. data/db/migrations/024_org_semaphore_jobs.rb +9 -0
  59. data/db/migrations/025_integration_backfill_cursor.rb +9 -0
  60. data/db/migrations/026_undo_integration_backfill_cursor.rb +9 -0
  61. data/db/migrations/027_sync_target_http_sync.rb +12 -0
  62. data/db/migrations/028_logged_webhook_path.rb +24 -0
  63. data/db/migrations/029_encrypt_columns.rb +97 -0
  64. data/db/migrations/030_org_sync_target_timeout.rb +9 -0
  65. data/db/migrations/031_org_max_query_rows.rb +9 -0
  66. data/db/migrations/032_remove_db_defaults.rb +12 -0
  67. data/db/migrations/033_backfill_jobs.rb +26 -0
  68. data/db/migrations/034_backfill_job_criteria.rb +9 -0
  69. data/db/migrations/035_synchronous_backfill.rb +9 -0
  70. data/db/migrations/036_oauth.rb +26 -0
  71. data/db/migrations/037_oauth_used.rb +9 -0
  72. data/lib/amigo/durable_job.rb +416 -0
  73. data/lib/pry/clipboard.rb +111 -0
  74. data/lib/sequel/advisory_lock.rb +65 -0
  75. data/lib/webhookdb/admin.rb +4 -0
  76. data/lib/webhookdb/admin_api/auth.rb +36 -0
  77. data/lib/webhookdb/admin_api/customers.rb +63 -0
  78. data/lib/webhookdb/admin_api/database_documents.rb +20 -0
  79. data/lib/webhookdb/admin_api/entities.rb +66 -0
  80. data/lib/webhookdb/admin_api/message_deliveries.rb +61 -0
  81. data/lib/webhookdb/admin_api/roles.rb +15 -0
  82. data/lib/webhookdb/admin_api.rb +34 -0
  83. data/lib/webhookdb/aggregate_result.rb +63 -0
  84. data/lib/webhookdb/api/auth.rb +122 -0
  85. data/lib/webhookdb/api/connstr_auth.rb +36 -0
  86. data/lib/webhookdb/api/db.rb +188 -0
  87. data/lib/webhookdb/api/demo.rb +14 -0
  88. data/lib/webhookdb/api/entities.rb +198 -0
  89. data/lib/webhookdb/api/helpers.rb +253 -0
  90. data/lib/webhookdb/api/install.rb +296 -0
  91. data/lib/webhookdb/api/me.rb +53 -0
  92. data/lib/webhookdb/api/organizations.rb +254 -0
  93. data/lib/webhookdb/api/replay.rb +64 -0
  94. data/lib/webhookdb/api/service_integrations.rb +402 -0
  95. data/lib/webhookdb/api/services.rb +27 -0
  96. data/lib/webhookdb/api/stripe.rb +22 -0
  97. data/lib/webhookdb/api/subscriptions.rb +67 -0
  98. data/lib/webhookdb/api/sync_targets.rb +232 -0
  99. data/lib/webhookdb/api/system.rb +37 -0
  100. data/lib/webhookdb/api/webhook_subscriptions.rb +96 -0
  101. data/lib/webhookdb/api.rb +92 -0
  102. data/lib/webhookdb/apps.rb +93 -0
  103. data/lib/webhookdb/async/audit_logger.rb +38 -0
  104. data/lib/webhookdb/async/autoscaler.rb +84 -0
  105. data/lib/webhookdb/async/job.rb +18 -0
  106. data/lib/webhookdb/async/job_logger.rb +45 -0
  107. data/lib/webhookdb/async/scheduled_job.rb +18 -0
  108. data/lib/webhookdb/async.rb +142 -0
  109. data/lib/webhookdb/aws.rb +98 -0
  110. data/lib/webhookdb/backfill_job.rb +107 -0
  111. data/lib/webhookdb/backfiller.rb +107 -0
  112. data/lib/webhookdb/cloudflare.rb +39 -0
  113. data/lib/webhookdb/connection_cache.rb +177 -0
  114. data/lib/webhookdb/console.rb +71 -0
  115. data/lib/webhookdb/convertkit.rb +14 -0
  116. data/lib/webhookdb/crypto.rb +66 -0
  117. data/lib/webhookdb/customer/reset_code.rb +94 -0
  118. data/lib/webhookdb/customer.rb +347 -0
  119. data/lib/webhookdb/database_document.rb +72 -0
  120. data/lib/webhookdb/db_adapter/column_types.rb +37 -0
  121. data/lib/webhookdb/db_adapter/default_sql.rb +187 -0
  122. data/lib/webhookdb/db_adapter/pg.rb +96 -0
  123. data/lib/webhookdb/db_adapter/snowflake.rb +137 -0
  124. data/lib/webhookdb/db_adapter.rb +208 -0
  125. data/lib/webhookdb/dbutil.rb +92 -0
  126. data/lib/webhookdb/demo_mode.rb +100 -0
  127. data/lib/webhookdb/developer_alert.rb +51 -0
  128. data/lib/webhookdb/email_octopus.rb +21 -0
  129. data/lib/webhookdb/enumerable.rb +18 -0
  130. data/lib/webhookdb/fixtures/backfill_jobs.rb +72 -0
  131. data/lib/webhookdb/fixtures/customers.rb +65 -0
  132. data/lib/webhookdb/fixtures/database_documents.rb +27 -0
  133. data/lib/webhookdb/fixtures/faker.rb +41 -0
  134. data/lib/webhookdb/fixtures/logged_webhooks.rb +56 -0
  135. data/lib/webhookdb/fixtures/message_deliveries.rb +59 -0
  136. data/lib/webhookdb/fixtures/oauth_sessions.rb +24 -0
  137. data/lib/webhookdb/fixtures/organization_database_migrations.rb +37 -0
  138. data/lib/webhookdb/fixtures/organization_memberships.rb +54 -0
  139. data/lib/webhookdb/fixtures/organizations.rb +32 -0
  140. data/lib/webhookdb/fixtures/reset_codes.rb +23 -0
  141. data/lib/webhookdb/fixtures/service_integrations.rb +42 -0
  142. data/lib/webhookdb/fixtures/subscriptions.rb +33 -0
  143. data/lib/webhookdb/fixtures/sync_targets.rb +32 -0
  144. data/lib/webhookdb/fixtures/webhook_subscriptions.rb +35 -0
  145. data/lib/webhookdb/fixtures.rb +15 -0
  146. data/lib/webhookdb/formatting.rb +56 -0
  147. data/lib/webhookdb/front.rb +49 -0
  148. data/lib/webhookdb/github.rb +22 -0
  149. data/lib/webhookdb/google_calendar.rb +29 -0
  150. data/lib/webhookdb/heroku.rb +21 -0
  151. data/lib/webhookdb/http.rb +114 -0
  152. data/lib/webhookdb/icalendar.rb +17 -0
  153. data/lib/webhookdb/id.rb +17 -0
  154. data/lib/webhookdb/idempotency.rb +90 -0
  155. data/lib/webhookdb/increase.rb +42 -0
  156. data/lib/webhookdb/intercom.rb +23 -0
  157. data/lib/webhookdb/jobs/amigo_test_jobs.rb +118 -0
  158. data/lib/webhookdb/jobs/backfill.rb +32 -0
  159. data/lib/webhookdb/jobs/create_mirror_table.rb +18 -0
  160. data/lib/webhookdb/jobs/create_stripe_customer.rb +17 -0
  161. data/lib/webhookdb/jobs/customer_created_notify_internal.rb +22 -0
  162. data/lib/webhookdb/jobs/demo_mode_sync_data.rb +19 -0
  163. data/lib/webhookdb/jobs/deprecated_jobs.rb +19 -0
  164. data/lib/webhookdb/jobs/developer_alert_handle.rb +14 -0
  165. data/lib/webhookdb/jobs/durable_job_recheck_poller.rb +17 -0
  166. data/lib/webhookdb/jobs/emailer.rb +15 -0
  167. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +25 -0
  168. data/lib/webhookdb/jobs/icalendar_sync.rb +23 -0
  169. data/lib/webhookdb/jobs/logged_webhook_replay.rb +17 -0
  170. data/lib/webhookdb/jobs/logged_webhook_resilient_replay.rb +15 -0
  171. data/lib/webhookdb/jobs/message_dispatched.rb +16 -0
  172. data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +21 -0
  173. data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +21 -0
  174. data/lib/webhookdb/jobs/organization_database_migration_run.rb +24 -0
  175. data/lib/webhookdb/jobs/prepare_database_connections.rb +22 -0
  176. data/lib/webhookdb/jobs/process_webhook.rb +47 -0
  177. data/lib/webhookdb/jobs/renew_watch_channel.rb +24 -0
  178. data/lib/webhookdb/jobs/replication_migration.rb +24 -0
  179. data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +23 -0
  180. data/lib/webhookdb/jobs/scheduled_backfills.rb +77 -0
  181. data/lib/webhookdb/jobs/send_invite.rb +15 -0
  182. data/lib/webhookdb/jobs/send_test_webhook.rb +25 -0
  183. data/lib/webhookdb/jobs/send_webhook.rb +20 -0
  184. data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +16 -0
  185. data/lib/webhookdb/jobs/sync_target_run_sync.rb +38 -0
  186. data/lib/webhookdb/jobs/trim_logged_webhooks.rb +15 -0
  187. data/lib/webhookdb/jobs/webhook_resource_notify_integrations.rb +30 -0
  188. data/lib/webhookdb/jobs/webhook_subscription_delivery_attempt.rb +29 -0
  189. data/lib/webhookdb/jobs.rb +4 -0
  190. data/lib/webhookdb/json.rb +113 -0
  191. data/lib/webhookdb/liquid/expose.rb +27 -0
  192. data/lib/webhookdb/liquid/filters.rb +16 -0
  193. data/lib/webhookdb/liquid/liquification.rb +26 -0
  194. data/lib/webhookdb/liquid/partial.rb +12 -0
  195. data/lib/webhookdb/logged_webhook/resilient.rb +95 -0
  196. data/lib/webhookdb/logged_webhook.rb +194 -0
  197. data/lib/webhookdb/message/body.rb +25 -0
  198. data/lib/webhookdb/message/delivery.rb +127 -0
  199. data/lib/webhookdb/message/email_transport.rb +133 -0
  200. data/lib/webhookdb/message/fake_transport.rb +54 -0
  201. data/lib/webhookdb/message/liquid_drops.rb +29 -0
  202. data/lib/webhookdb/message/template.rb +89 -0
  203. data/lib/webhookdb/message/transport.rb +43 -0
  204. data/lib/webhookdb/message.rb +150 -0
  205. data/lib/webhookdb/messages/error_icalendar_fetch.rb +42 -0
  206. data/lib/webhookdb/messages/invite.rb +23 -0
  207. data/lib/webhookdb/messages/new_customer.rb +14 -0
  208. data/lib/webhookdb/messages/org_database_migration_finished.rb +23 -0
  209. data/lib/webhookdb/messages/org_database_migration_started.rb +24 -0
  210. data/lib/webhookdb/messages/specs.rb +57 -0
  211. data/lib/webhookdb/messages/verification.rb +23 -0
  212. data/lib/webhookdb/method_utilities.rb +82 -0
  213. data/lib/webhookdb/microsoft_calendar.rb +36 -0
  214. data/lib/webhookdb/nextpax.rb +14 -0
  215. data/lib/webhookdb/oauth/front.rb +58 -0
  216. data/lib/webhookdb/oauth/intercom.rb +58 -0
  217. data/lib/webhookdb/oauth/session.rb +24 -0
  218. data/lib/webhookdb/oauth.rb +80 -0
  219. data/lib/webhookdb/organization/alerting.rb +35 -0
  220. data/lib/webhookdb/organization/database_migration.rb +151 -0
  221. data/lib/webhookdb/organization/db_builder.rb +429 -0
  222. data/lib/webhookdb/organization.rb +506 -0
  223. data/lib/webhookdb/organization_membership.rb +58 -0
  224. data/lib/webhookdb/phone_number.rb +38 -0
  225. data/lib/webhookdb/plaid.rb +23 -0
  226. data/lib/webhookdb/platform.rb +27 -0
  227. data/lib/webhookdb/plivo.rb +52 -0
  228. data/lib/webhookdb/postgres/maintenance.rb +166 -0
  229. data/lib/webhookdb/postgres/model.rb +82 -0
  230. data/lib/webhookdb/postgres/model_utilities.rb +382 -0
  231. data/lib/webhookdb/postgres/testing_pixie.rb +16 -0
  232. data/lib/webhookdb/postgres/validations.rb +46 -0
  233. data/lib/webhookdb/postgres.rb +176 -0
  234. data/lib/webhookdb/postmark.rb +20 -0
  235. data/lib/webhookdb/redis.rb +35 -0
  236. data/lib/webhookdb/replicator/atom_single_feed_v1.rb +116 -0
  237. data/lib/webhookdb/replicator/aws_pricing_v1.rb +488 -0
  238. data/lib/webhookdb/replicator/base.rb +1185 -0
  239. data/lib/webhookdb/replicator/column.rb +482 -0
  240. data/lib/webhookdb/replicator/convertkit_broadcast_v1.rb +69 -0
  241. data/lib/webhookdb/replicator/convertkit_subscriber_v1.rb +200 -0
  242. data/lib/webhookdb/replicator/convertkit_tag_v1.rb +66 -0
  243. data/lib/webhookdb/replicator/convertkit_v1_mixin.rb +65 -0
  244. data/lib/webhookdb/replicator/docgen.rb +167 -0
  245. data/lib/webhookdb/replicator/email_octopus_campaign_v1.rb +84 -0
  246. data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +159 -0
  247. data/lib/webhookdb/replicator/email_octopus_event_v1.rb +244 -0
  248. data/lib/webhookdb/replicator/email_octopus_list_v1.rb +101 -0
  249. data/lib/webhookdb/replicator/fake.rb +453 -0
  250. data/lib/webhookdb/replicator/front_conversation_v1.rb +45 -0
  251. data/lib/webhookdb/replicator/front_marketplace_root_v1.rb +55 -0
  252. data/lib/webhookdb/replicator/front_message_v1.rb +45 -0
  253. data/lib/webhookdb/replicator/front_v1_mixin.rb +22 -0
  254. data/lib/webhookdb/replicator/github_issue_comment_v1.rb +58 -0
  255. data/lib/webhookdb/replicator/github_issue_v1.rb +83 -0
  256. data/lib/webhookdb/replicator/github_pull_v1.rb +84 -0
  257. data/lib/webhookdb/replicator/github_release_v1.rb +47 -0
  258. data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +250 -0
  259. data/lib/webhookdb/replicator/github_repository_event_v1.rb +45 -0
  260. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +465 -0
  261. data/lib/webhookdb/replicator/icalendar_event_v1.rb +334 -0
  262. data/lib/webhookdb/replicator/increase_account_number_v1.rb +77 -0
  263. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +61 -0
  264. data/lib/webhookdb/replicator/increase_account_v1.rb +63 -0
  265. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +78 -0
  266. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +64 -0
  267. data/lib/webhookdb/replicator/increase_limit_v1.rb +78 -0
  268. data/lib/webhookdb/replicator/increase_transaction_v1.rb +74 -0
  269. data/lib/webhookdb/replicator/increase_v1_mixin.rb +121 -0
  270. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +61 -0
  271. data/lib/webhookdb/replicator/intercom_contact_v1.rb +36 -0
  272. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +38 -0
  273. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +69 -0
  274. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +105 -0
  275. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +65 -0
  276. data/lib/webhookdb/replicator/plivo_sms_inbound_v1.rb +102 -0
  277. data/lib/webhookdb/replicator/postmark_inbound_message_v1.rb +94 -0
  278. data/lib/webhookdb/replicator/postmark_outbound_message_event_v1.rb +107 -0
  279. data/lib/webhookdb/replicator/schema_modification.rb +42 -0
  280. data/lib/webhookdb/replicator/shopify_customer_v1.rb +58 -0
  281. data/lib/webhookdb/replicator/shopify_order_v1.rb +64 -0
  282. data/lib/webhookdb/replicator/shopify_v1_mixin.rb +161 -0
  283. data/lib/webhookdb/replicator/signalwire_message_v1.rb +169 -0
  284. data/lib/webhookdb/replicator/sponsy_customer_v1.rb +54 -0
  285. data/lib/webhookdb/replicator/sponsy_placement_v1.rb +34 -0
  286. data/lib/webhookdb/replicator/sponsy_publication_v1.rb +125 -0
  287. data/lib/webhookdb/replicator/sponsy_slot_v1.rb +41 -0
  288. data/lib/webhookdb/replicator/sponsy_status_v1.rb +35 -0
  289. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +165 -0
  290. data/lib/webhookdb/replicator/state_machine_step.rb +69 -0
  291. data/lib/webhookdb/replicator/stripe_charge_v1.rb +77 -0
  292. data/lib/webhookdb/replicator/stripe_coupon_v1.rb +62 -0
  293. data/lib/webhookdb/replicator/stripe_customer_v1.rb +60 -0
  294. data/lib/webhookdb/replicator/stripe_dispute_v1.rb +77 -0
  295. data/lib/webhookdb/replicator/stripe_invoice_item_v1.rb +82 -0
  296. data/lib/webhookdb/replicator/stripe_invoice_v1.rb +116 -0
  297. data/lib/webhookdb/replicator/stripe_payout_v1.rb +67 -0
  298. data/lib/webhookdb/replicator/stripe_price_v1.rb +60 -0
  299. data/lib/webhookdb/replicator/stripe_product_v1.rb +60 -0
  300. data/lib/webhookdb/replicator/stripe_refund_v1.rb +101 -0
  301. data/lib/webhookdb/replicator/stripe_subscription_item_v1.rb +56 -0
  302. data/lib/webhookdb/replicator/stripe_subscription_v1.rb +75 -0
  303. data/lib/webhookdb/replicator/stripe_v1_mixin.rb +116 -0
  304. data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +141 -0
  305. data/lib/webhookdb/replicator/transistor_episode_v1.rb +169 -0
  306. data/lib/webhookdb/replicator/transistor_show_v1.rb +68 -0
  307. data/lib/webhookdb/replicator/transistor_v1_mixin.rb +65 -0
  308. data/lib/webhookdb/replicator/twilio_sms_v1.rb +156 -0
  309. data/lib/webhookdb/replicator/webhook_request.rb +5 -0
  310. data/lib/webhookdb/replicator/webhookdb_customer_v1.rb +74 -0
  311. data/lib/webhookdb/replicator.rb +224 -0
  312. data/lib/webhookdb/role.rb +42 -0
  313. data/lib/webhookdb/sentry.rb +35 -0
  314. data/lib/webhookdb/service/auth.rb +138 -0
  315. data/lib/webhookdb/service/collection.rb +91 -0
  316. data/lib/webhookdb/service/entities.rb +97 -0
  317. data/lib/webhookdb/service/helpers.rb +270 -0
  318. data/lib/webhookdb/service/middleware.rb +124 -0
  319. data/lib/webhookdb/service/types.rb +30 -0
  320. data/lib/webhookdb/service/validators.rb +32 -0
  321. data/lib/webhookdb/service/view_api.rb +63 -0
  322. data/lib/webhookdb/service.rb +219 -0
  323. data/lib/webhookdb/service_integration.rb +332 -0
  324. data/lib/webhookdb/shopify.rb +35 -0
  325. data/lib/webhookdb/signalwire.rb +13 -0
  326. data/lib/webhookdb/slack.rb +68 -0
  327. data/lib/webhookdb/snowflake.rb +90 -0
  328. data/lib/webhookdb/spec_helpers/async.rb +122 -0
  329. data/lib/webhookdb/spec_helpers/citest.rb +88 -0
  330. data/lib/webhookdb/spec_helpers/integration.rb +121 -0
  331. data/lib/webhookdb/spec_helpers/message.rb +41 -0
  332. data/lib/webhookdb/spec_helpers/postgres.rb +220 -0
  333. data/lib/webhookdb/spec_helpers/service.rb +432 -0
  334. data/lib/webhookdb/spec_helpers/shared_examples_for_columns.rb +56 -0
  335. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +915 -0
  336. data/lib/webhookdb/spec_helpers/whdb.rb +139 -0
  337. data/lib/webhookdb/spec_helpers.rb +63 -0
  338. data/lib/webhookdb/sponsy.rb +14 -0
  339. data/lib/webhookdb/stripe.rb +37 -0
  340. data/lib/webhookdb/subscription.rb +203 -0
  341. data/lib/webhookdb/sync_target.rb +491 -0
  342. data/lib/webhookdb/tasks/admin.rb +49 -0
  343. data/lib/webhookdb/tasks/annotate.rb +36 -0
  344. data/lib/webhookdb/tasks/db.rb +82 -0
  345. data/lib/webhookdb/tasks/docs.rb +42 -0
  346. data/lib/webhookdb/tasks/fixture.rb +35 -0
  347. data/lib/webhookdb/tasks/message.rb +50 -0
  348. data/lib/webhookdb/tasks/regress.rb +87 -0
  349. data/lib/webhookdb/tasks/release.rb +27 -0
  350. data/lib/webhookdb/tasks/sidekiq.rb +23 -0
  351. data/lib/webhookdb/tasks/specs.rb +64 -0
  352. data/lib/webhookdb/theranest.rb +15 -0
  353. data/lib/webhookdb/transistor.rb +13 -0
  354. data/lib/webhookdb/twilio.rb +13 -0
  355. data/lib/webhookdb/typed_struct.rb +44 -0
  356. data/lib/webhookdb/version.rb +5 -0
  357. data/lib/webhookdb/webhook_response.rb +50 -0
  358. data/lib/webhookdb/webhook_subscription/delivery.rb +82 -0
  359. data/lib/webhookdb/webhook_subscription.rb +226 -0
  360. data/lib/webhookdb/windows_tz.rb +32 -0
  361. data/lib/webhookdb/xml.rb +92 -0
  362. data/lib/webhookdb.rb +224 -0
  363. data/lib/webterm/apps.rb +45 -0
  364. metadata +1129 -0
@@ -0,0 +1,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