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,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/jobs/webhook_subscription_delivery_attempt"
4
+
5
+ # Webhook subscriptions have a few parts:
6
+ #
7
+ # - The WebhookSubscription itself (this model),
8
+ # which represents a user's desire to receive all webhooks at a URL.
9
+ # - The individual Delivery, which is a single 'rowupsert' event being
10
+ # delivered to a subscription.
11
+ # That is, if multiple rowupserts are done, there will be multiple Deliveries
12
+ # to a single Subscription, one for each rowupsert.
13
+ # Likewise, if a single rowupsert is done, but there are multiple Subscriptions,
14
+ # there will be multiple Deliveries, one to each Subscription.
15
+ # - Async job that listens for rowupsert events and enqueues new deliveries.
16
+ # - When a delivery is 'enqueued', it is created in the database,
17
+ # and then a sidekiq job is put into Redis.
18
+ # This sidekiq job operates OUTSIDE of our normal job system
19
+ # since we do not want to bother with audit logging or routing
20
+ # (enough history is in the DB already, though we could add it if needed).
21
+ # - We attempt the delivery until it succeeds, or we run out of attempts.
22
+ # See #attempt_delivery.
23
+ #
24
+ class Webhookdb::WebhookSubscription < Webhookdb::Postgres::Model(:webhook_subscriptions)
25
+ plugin :timestamps
26
+ plugin :column_encryption do |enc|
27
+ enc.column :webhook_secret
28
+ end
29
+
30
+ many_to_one :service_integration, class: Webhookdb::ServiceIntegration
31
+ many_to_one :organization, class: Webhookdb::Organization
32
+ many_to_one :created_by, class: Webhookdb::Customer
33
+
34
+ # Amount of time we wait for a response from the server.
35
+ TIMEOUT = 10.seconds
36
+ # An individual will be delivered this many times before giving up.
37
+ MAX_DELIVERY_ATTEMPTS = 25
38
+
39
+ dataset_module do
40
+ def active
41
+ return self.where(deactivated_at: nil)
42
+ end
43
+
44
+ def to_notify
45
+ return self.active
46
+ end
47
+ end
48
+
49
+ def active?
50
+ return !self.deactivated?
51
+ end
52
+
53
+ def deactivated?
54
+ return !!self.deactivated_at
55
+ end
56
+
57
+ def deactivate(at: Time.now)
58
+ self.deactivated_at = at
59
+ return self
60
+ end
61
+
62
+ def fetch_organization
63
+ return self.organization || self.service_integration.organization
64
+ end
65
+
66
+ def status
67
+ return self.deactivated? ? "deactivated" : "active"
68
+ end
69
+
70
+ # Deliver the webhook payload to the configured URL.
71
+ # This does NOT create or deal with WebhookSubscription::Delivery;
72
+ # it is for the actual delivering.
73
+ def deliver(service_name:, table_name:, row:, external_id:, external_id_column:, headers: {})
74
+ body = {
75
+ service_name:,
76
+ table_name:,
77
+ row:,
78
+ external_id:,
79
+ external_id_column:,
80
+ }
81
+ return Webhookdb::Http.post(
82
+ self.deliver_to_url,
83
+ body,
84
+ headers: {"Whdb-Webhook-Secret" => self.webhook_secret}.merge(headers),
85
+ timeout: TIMEOUT,
86
+ logger: self.logger,
87
+ )
88
+ end
89
+
90
+ def deliver_test_event(external_id: SecureRandom.hex(6))
91
+ return self.deliver(
92
+ service_name: "test service",
93
+ table_name: "test_table_name",
94
+ external_id:,
95
+ external_id_column: "external_id",
96
+ row: {data: ["alpha", "beta", "charlie", "delta"]},
97
+ headers: {"Whdb-Test-Event" => "1"},
98
+ )
99
+ end
100
+
101
+ def create_delivery(payload)
102
+ return Webhookdb::WebhookSubscription::Delivery.create(webhook_subscription: self, payload:)
103
+ end
104
+
105
+ # Create a new Delivery and enqueue it for async processing.
106
+ def enqueue_delivery(payload)
107
+ delivery = self.create_delivery(payload)
108
+ Webhookdb::Jobs::WebhookSubscriptionDeliveryEvent.perform_async(delivery.id)
109
+ return delivery
110
+ end
111
+
112
+ # Attempt to deliver the payload in +d+ to the configured URL (see #deliver).
113
+ # Noops if the subscription is deactivated.
114
+ #
115
+ # If the attempt succeeds, no attempts are enqueued.
116
+ #
117
+ # If the attempt fails, another async job to reattempt delivery
118
+ # will be enqueued for some time in the future based on the number of attempts.
119
+ # The timestamp and http status are stored on the delivery for future analysis.
120
+ #
121
+ # After too many failures, no more attempts will be enqueued.
122
+ # Instead, a developer alert is emitted.
123
+ #
124
+ # In the future, we will support manually re-attempting delivery (success of which should
125
+ # clear deactivated subscriptions), and automatic deactivation
126
+ # (after some criteria of abandonment has been met).
127
+ def attempt_delivery(d)
128
+ return if self.deactivated?
129
+ d.db.transaction do
130
+ d.lock!
131
+ attempt = d.attempt_count + 1
132
+ begin
133
+ r = self.deliver(**d.payload.symbolize_keys, headers: {"Whdb-Attempt" => attempt.to_s})
134
+ d.add_attempt(status: r.code)
135
+ rescue StandardError => e
136
+ self.logger.error(
137
+ "webhook_subscription_delivery_failure",
138
+ error: e,
139
+ webhook_subscription_id: self.id,
140
+ webhook_subscription_delivery_id: d.id,
141
+ )
142
+ d.add_attempt(status: e.is_a?(Webhookdb::Http::Error) ? e.status : 0)
143
+ if attempt < MAX_DELIVERY_ATTEMPTS
144
+ self._retry(d, attempt)
145
+ else
146
+ self._fatal(d, e)
147
+ end
148
+ ensure
149
+ d.save_changes
150
+ end
151
+ end
152
+ end
153
+
154
+ def _retry(delivery, attempt)
155
+ delay = self.class.backoff_for_attempt(attempt)
156
+ Webhookdb::Jobs::WebhookSubscriptionDeliveryEvent.perform_in(delay, delivery.id)
157
+ end
158
+
159
+ def self.backoff_for_attempt(attempt)
160
+ return 1 if attempt <= 1
161
+ return attempt * 2 if attempt <= 10
162
+ return attempt * 3 if attempt <= 20
163
+ return attempt * 4
164
+ end
165
+
166
+ def _fatal(d, e)
167
+ Webhookdb::DeveloperAlert.new(
168
+ subsystem: "Webhook Subscriptions",
169
+ emoji: ":hook:",
170
+ fallback: "Error delivering WebhookSubscription::Delivery[id: #{d.id}, subscription_id: #{self.id}]: #{e}",
171
+ fields: [
172
+ {title: "Org", value: self.fetch_organization.display_string, short: true},
173
+ {title: "Creator", value: self.created_by&.email, short: true},
174
+ {title: "Delivery", value: "#{d.id}, Subscription: #{self.id}, Attempts: #{d.attempt_count}"},
175
+ {title: "URL", value: self.deliver_to_url, short: false},
176
+ {title: "Exception", value: e.inspect, short: false},
177
+ ],
178
+ ).emit
179
+ end
180
+
181
+ def associated_type
182
+ return "organization" unless self.organization_id.nil?
183
+ return "service_integration" unless self.service_integration_id.nil?
184
+ return ""
185
+ end
186
+
187
+ def associated_id
188
+ return self.organization.key unless self.organization_id.nil?
189
+ return self.service_integration.opaque_id unless self.service_integration_id.nil?
190
+ return ""
191
+ end
192
+
193
+ #
194
+ # :Sequel Hooks:
195
+ #
196
+
197
+ def before_create
198
+ self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("wsb")
199
+ end
200
+ end
201
+
202
+ # Table: webhook_subscriptions
203
+ # ----------------------------------------------------------------------------------------------------------------------------------------------------------------
204
+ # Columns:
205
+ # id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
206
+ # deliver_to_url | text | NOT NULL
207
+ # webhook_secret | text | NOT NULL
208
+ # opaque_id | text | NOT NULL
209
+ # service_integration_id | integer |
210
+ # organization_id | integer |
211
+ # created_at | timestamp with time zone | NOT NULL DEFAULT now()
212
+ # updated_at | timestamp with time zone |
213
+ # created_by_id | integer |
214
+ # deactivated_at | timestamp with time zone |
215
+ # Indexes:
216
+ # webhook_subscriptions_pkey | PRIMARY KEY btree (id)
217
+ # webhook_subscriptions_opaque_id_key | UNIQUE btree (opaque_id)
218
+ # Check constraints:
219
+ # service_integration_or_org | (service_integration_id IS NULL AND organization_id IS NOT NULL OR service_integration_id IS NOT NULL AND organization_id IS NULL)
220
+ # Foreign key constraints:
221
+ # webhook_subscriptions_created_by_id_fkey | (created_by_id) REFERENCES customers(id)
222
+ # webhook_subscriptions_organization_id_fkey | (organization_id) REFERENCES organizations(id)
223
+ # webhook_subscriptions_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id)
224
+ # Referenced By:
225
+ # webhook_subscription_deliveries | webhook_subscription_deliveries_webhook_subscription_id_fkey | (webhook_subscription_id) REFERENCES webhook_subscriptions(id)
226
+ # ----------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Map Windows Timezone names to IANA names (and ActiveSupport timezones).
4
+ # Outlook calendar uses these timezones.
5
+ module Webhookdb::WindowsTZ
6
+ class << self
7
+ attr_accessor :_win_to_tz
8
+
9
+ # @return [Hash<String => ActiveSupport::TimeZone>]
10
+ def windows_name_to_tz
11
+ return self._win_to_tz if self._win_to_tz
12
+ win_to_tz = {}
13
+ self._win_to_tz = win_to_tz
14
+
15
+ all_win_names = Set.new
16
+ File.open(Webhookdb::DATA_DIR + "windows_tz.txt").each do |line|
17
+ line.strip!
18
+ next if line.blank? || line.start_with?("#")
19
+ iana, win = line.split(/\s/, 2)
20
+ next if win_to_tz.include?(win)
21
+ all_win_names.add(win)
22
+ tz = ActiveSupport::TimeZone[iana]
23
+ next if tz.nil?
24
+ win_to_tz[win] = tz
25
+ end
26
+ win_to_tz.each_key { |k| all_win_names.delete(k) }
27
+ raise Webhookdb::InvariantViolation, "unmapped windows timezones: #{all_win_names.join(', ')}" unless
28
+ all_win_names.empty?
29
+ return win_to_tz
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Xml
4
+ module Atom
5
+ def self.parse(thing)
6
+ Parser.new(thing).to_hash
7
+ end
8
+
9
+ def self.parse_entry(thing)
10
+ p = Parser.new(thing)
11
+ p.parse_entry(p.doc.root)
12
+ end
13
+
14
+ class Parser
15
+ attr_reader :doc
16
+
17
+ def initialize(thing)
18
+ @doc = Nokogiri::XML.parse(thing, &:noblanks)
19
+ end
20
+
21
+ def to_hash
22
+ entries = []
23
+ feed = {"entries" => entries}
24
+ @doc.root.children.each do |c|
25
+ if c.is_a?(Nokogiri::XML::Text)
26
+ next
27
+ elsif c.name == "entry"
28
+ entries << self.parse_entry(c)
29
+ elsif self.spec_attr?(c)
30
+ feed[self.fqn(c)] = self.parse_spec_attr(c)
31
+ elsif self.simple_text?(c)
32
+ feed[self.fqn(c)] = self.text(c)
33
+ else
34
+ feed[self.fqn(c)] = self.parse_to_hash(c)
35
+ end
36
+ end
37
+ return feed
38
+ end
39
+
40
+ protected def simple_text?(c) = c.children.size == 1 && c.children[0].is_a?(Nokogiri::XML::Text)
41
+ protected def spec_attr?(c) = ["link", "category"].include?(c.name)
42
+
43
+ protected def parse_spec_attr(c)
44
+ h = {}
45
+ c.attributes.each do |k, v|
46
+ h[k] = v.value
47
+ end
48
+ h["text"] = self.text(c) if simple_text?(c)
49
+ return h
50
+ end
51
+
52
+ def parse_entry(e)
53
+ h = {}
54
+ e.children.each do |c|
55
+ if c.name == "content"
56
+ content = {}
57
+ h["content"] = content
58
+ content["value"] = c.children.to_s if c.children.to_s.present?
59
+ c.attributes.each do |k, v|
60
+ content[k] = v.value
61
+ end
62
+ elsif self.spec_attr?(c)
63
+ h[self.fqn(c)] = self.parse_spec_attr(c)
64
+ else
65
+ h[self.fqn(c)] = c.text
66
+ end
67
+ end
68
+ return h
69
+ end
70
+
71
+ protected def fqn(c)
72
+ return c.name unless c.namespace&.prefix
73
+ return "#{c.namespace.prefix}:#{c.name}"
74
+ end
75
+
76
+ protected def parse_to_hash(c)
77
+ h = {}
78
+ c.children.each do |cc|
79
+ h[self.fqn(cc)] = self.text(cc)
80
+ end
81
+ return h
82
+ end
83
+
84
+ protected def text(c)
85
+ t = c.children.first
86
+ return "" if t.nil?
87
+ raise ArgumentError, "child is not text: #{c}" unless t.is_a?(Nokogiri::XML::Text)
88
+ return t.text
89
+ end
90
+ end
91
+ end
92
+ end
data/lib/webhookdb.rb ADDED
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext"
5
+ require "appydays/configurable"
6
+ require "appydays/loggable"
7
+ require "money"
8
+ require "pathname"
9
+ require "phony"
10
+
11
+ require "webhookdb/json"
12
+
13
+ if (heroku_app = ENV.fetch("MERGE_HEROKU_ENV", nil))
14
+ text = `heroku config -j --app=#{heroku_app}`
15
+ json = Oj.load(text)
16
+ json.each do |k, v|
17
+ ENV[k] = v
18
+ end
19
+ end
20
+
21
+ Money.locale_backend = :i18n
22
+ Money.default_currency = "USD"
23
+ Money.rounding_mode = BigDecimal::ROUND_HALF_UP
24
+
25
+ module Webhookdb
26
+ include Appydays::Loggable
27
+ include Appydays::Configurable
28
+
29
+ # Error raised when we cannot take an action
30
+ # because some condition has not been set up right.
31
+ class InvalidPrecondition < StandardError; end
32
+
33
+ # Error raised when, after we take an action,
34
+ # something we expect to have changed has not changed.
35
+ class InvalidPostcondition < StandardError; end
36
+
37
+ # Some invariant has been violated, which we never expect to see.
38
+ class InvariantViolation < StandardError; end
39
+
40
+ # Error raised when a customer gives us some invalid input.
41
+ # Allows the library to raise the error with the message,
42
+ # and is caught automatically by the service as a 400.
43
+ class InvalidInput < StandardError; end
44
+
45
+ # Raised when an organization's database cannot be modified.
46
+ class DatabaseLocked < StandardError; end
47
+
48
+ # Used in various places that need to short-circuit code in regression mode.
49
+ class RegressionModeSkip < StandardError; end
50
+
51
+ APPLICATION_NAME = "Webhookdb"
52
+ RACK_ENV = ENV.fetch("RACK_ENV", "development")
53
+ COMMIT = ENV.fetch("HEROKU_SLUG_COMMIT", "unknown-commit")
54
+ RELEASE = ENV.fetch("HEROKU_RELEASE_VERSION", "unknown-release")
55
+ RELEASE_CREATED_AT = ENV.fetch("HEROKU_RELEASE_CREATED_AT") { Time.at(0).utc.iso8601 }
56
+ INTEGRATION_TESTS_ENABLED = ENV.fetch("INTEGRATION_TESTS", false)
57
+
58
+ DATA_DIR = Pathname(__FILE__).dirname.parent + "data"
59
+
60
+ configurable(:webhookdb) do
61
+ setting :log_level_override,
62
+ nil,
63
+ key: "LOG_LEVEL",
64
+ side_effect: ->(v) { Appydays::Loggable.default_level = v if v }
65
+ setting :log_format, nil
66
+ setting :app_url, "http://localhost:18002"
67
+ setting :api_url, "http://localhost:#{ENV.fetch('PORT', 18_001)}"
68
+ setting :bust_idempotency, false
69
+ setting :http_user_agent, ""
70
+ setting :oss_repo_url, "https://github.com/webhookdb/webhookdb"
71
+ setting :support_email, "hello@webhookdb.com"
72
+ setting :use_globals_cache, false
73
+ setting :regression_mode, false
74
+ end
75
+
76
+ # Regression mode is true when we re replaying webhooks locally,
77
+ # or for some other reason, want to disable certain checks we use in production.
78
+ # For example, we may want to ignore certain errors (like if integrations are missing dependency rows),
79
+ # or disable certain validations (like always assume the webhook is valid).
80
+ def self.regression_mode?
81
+ return self.regression_mode
82
+ end
83
+
84
+ require "webhookdb/method_utilities"
85
+ extend Webhookdb::MethodUtilities
86
+
87
+ require "webhookdb/sentry"
88
+
89
+ def self.load_app
90
+ $stdout.sync = true
91
+ $stderr.sync = true
92
+
93
+ Appydays::Loggable.configure_12factor(format: self.log_format, application: APPLICATION_NAME)
94
+
95
+ require "webhookdb/postgres"
96
+ Webhookdb::Postgres.load_models
97
+ end
98
+
99
+ #
100
+ # :section: Globals cache
101
+ #
102
+
103
+ singleton_attr_reader :globals_cache
104
+ @globals_cache = {}
105
+
106
+ # If globals caching is enabled, see if there is a cached value under +key+
107
+ # and return it if so. If there is not, evaluate the given block and store that value.
108
+ # Generally used for looking up well-known database objects like certain roles.
109
+ def self.cached_get(key)
110
+ if self.use_globals_cache
111
+ result = self.globals_cache[key]
112
+ return result if result
113
+ end
114
+ result = yield()
115
+ self.globals_cache[key] = result
116
+ return result
117
+ end
118
+
119
+ #
120
+ # :section: Errors
121
+ #
122
+
123
+ class LockFailed < StandardError; end
124
+
125
+ ### Generate a key for the specified Sequel model +instance+ and
126
+ ### any additional +parts+ that can be used for idempotent requests.
127
+ def self.idempotency_key(instance, *parts)
128
+ key = "%s-%s" % [instance.class.implicit_table_name, instance.pk]
129
+
130
+ if instance.respond_to?(:updated_at) && instance.updated_at
131
+ parts << instance.updated_at
132
+ elsif instance.respond_to?(:created_at) && instance.created_at
133
+ parts << instance.created_at
134
+ end
135
+ parts << SecureRandom.hex(8) if self.bust_idempotency
136
+ key << "-" << parts.map(&:to_s).join("-") unless parts.empty?
137
+
138
+ return key
139
+ end
140
+
141
+ #
142
+ # :section: Unambiguous/promo code chars
143
+ #
144
+
145
+ # Remove ambiguous characters (L, I, 1 or 0, O) and vowels from possible codes
146
+ # to avoid creating ambiguous codes or real words.
147
+ UNAMBIGUOUS_CHARS = "CDFGHJKMNPQRTVWXYZ23469".chars.freeze
148
+
149
+ def self.take_unambiguous_chars(n)
150
+ return Array.new(n) { UNAMBIGUOUS_CHARS.sample }.join
151
+ end
152
+
153
+ # Convert a string into something we consistently use for slugs:
154
+ # a-z, 0-9, and underscores only. Leading numbers are converted to words.
155
+ #
156
+ # Acme + Corporation -> "acme_corporation"
157
+ # 1Byte -> "one_byte"
158
+ # 10Byte -> "one0_byte"
159
+ def self.to_slug(s)
160
+ raise ArgumentError, "s cannot be nil" if s.nil?
161
+ return "" if s.blank?
162
+ slug = s.downcase.strip.gsub(/[^a-z0-9]/, "_").squeeze("_")
163
+ slug = NUMBERS_TO_WORDS[slug.first] + slug[1..] if slug.first.match?(/[0-9]/)
164
+ return slug
165
+ end
166
+
167
+ NUMBERS_TO_WORDS = {
168
+ "0" => "zero",
169
+ "1" => "one",
170
+ "2" => "two",
171
+ "3" => "three",
172
+ "4" => "four",
173
+ "5" => "five",
174
+ "6" => "six",
175
+ "7" => "seven",
176
+ "8" => "eight",
177
+ "9" => "nine",
178
+ }.freeze
179
+
180
+ # Return the request user and admin stored in TLS. See service.rb for implementation.
181
+ #
182
+ # Note that the second return value (the admin) will be nil if not authed as an admin,
183
+ # and if an admin is impersonating, the impersonated customer is the first value.
184
+ #
185
+ # Both values will be nil if no user is authed or this is called outside of a request.
186
+ #
187
+ # Usually these fields should only be used where it would be sufficiently difficult
188
+ # to pass the current user through the stack.
189
+ # In the API, you should instead use the 'current customer' methods
190
+ # like current_customer, and admin_customer, NOT using TLS.
191
+ # Outside of the API, this should only be used for things like auditing;
192
+ # it should NOT, for example, ever be used to determine the 'customer owner' of objects
193
+ # being created. Nearly all code will be simpler if the current customer
194
+ # is passed around. But it would be too complex for some code (like auditing)
195
+ # so this system exists. Overuse of request_user_and_admin will inevitably lead to regret.
196
+ def self.request_user_and_admin
197
+ return Thread.current[:request_user], Thread.current[:request_admin]
198
+ end
199
+
200
+ # Return the request user stored in TLS. See service.rb for details.
201
+ def self.set_request_user_and_admin(user, admin, &block)
202
+ if !user.nil? && !admin.nil? && self.request_user_and_admin != [nil, nil]
203
+ raise Webhookdb::InvalidPrecondition, "request user is already set: #{user}, #{admin}"
204
+ end
205
+ Thread.current[:request_user] = user
206
+ Thread.current[:request_admin] = admin
207
+ return if block.nil?
208
+ begin
209
+ yield
210
+ ensure
211
+ Thread.current[:request_user] = nil
212
+ Thread.current[:request_admin] = nil
213
+ end
214
+ end
215
+ end
216
+
217
+ require "webhookdb/aggregate_result"
218
+ require "webhookdb/dbutil"
219
+ require "webhookdb/developer_alert"
220
+ require "webhookdb/http"
221
+ require "webhookdb/phone_number"
222
+ require "webhookdb/replicator"
223
+ require "webhookdb/typed_struct"
224
+ require "webhookdb/webhook_response"
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appydays/configurable"
4
+
5
+ class Webhookdb::Webterm
6
+ include Appydays::Configurable
7
+
8
+ configurable(:webterm) do
9
+ setting :enforce_ssl, true
10
+ end
11
+
12
+ STATIC = File.expand_path("#{File.dirname(__FILE__)}/static")
13
+
14
+ Files = Rack::Files.new(STATIC)
15
+
16
+ class RedirectIndexHtmlToRoot
17
+ REDIRECTS = ["", "/index.html"].freeze
18
+
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(env)
24
+ return [302, {"Location" => "/terminal/"}, []] if REDIRECTS.include?(env[Rack::PATH_INFO])
25
+ return @app.call(env)
26
+ end
27
+ end
28
+
29
+ class ServeIndexHtmlFromRoot
30
+ def initialize(app)
31
+ @app = app
32
+ @cached_html = nil
33
+ end
34
+
35
+ def call(env)
36
+ return @app.call(env) unless env[Rack::PATH_INFO] == "/"
37
+ if @cached_html.nil?
38
+ html = File.read(File.join(STATIC, "index.html"))
39
+ html.sub!("/* REPLACE_WHDB_ENV */", "window.whdbEnv = [['WEBHOOKDB_API_HOST', '#{Webhookdb.api_url}']]")
40
+ @cached_html = html.chars
41
+ end
42
+ return [200, {"Content-Type" => "text/html"}, @cached_html]
43
+ end
44
+ end
45
+ end