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,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/api"
4
+ require "webhookdb/jobs/sync_target_run_sync"
5
+
6
+ # rubocop:disable Layout/LineLength
7
+ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
8
+ class ConnectionUrlType < Grape::Validations::Validators::Base
9
+ def validate!(params)
10
+ url = params[:connection_url]
11
+ case @option
12
+ when "db", "http"
13
+ if (err = Webhookdb::SyncTarget.send(:"validate_#{@option}_url", url))
14
+ raise Grape::Exceptions::Validation.new params: [url], message: err
15
+ end
16
+ else
17
+ raise Grape::Exceptions::Validation.new params: [url], message: "#{@option} is not a valid connection url type"
18
+ end
19
+ end
20
+ end
21
+
22
+ resource :organizations do
23
+ route_param :org_identifier, type: String do
24
+ resource :sync_targets do
25
+ [:db, :http].each do |target_type_resource|
26
+ resource target_type_resource do
27
+ route_setting :target_type, {key: target_type_resource}
28
+ helpers do
29
+ is_db = target_type_resource == :db
30
+ def target_type
31
+ tgttype = route.settings[:target_type]
32
+ # Doesn't appear that this cascades, so we need to specify on every verb method.
33
+ raise "add `route_setting :target_type, target_type_resource` before verb" unless tgttype
34
+ return tgttype
35
+ end
36
+
37
+ def db? = target_type == :db
38
+
39
+ default_period = Webhookdb::SyncTarget.default_valid_period
40
+ params :sync_target_params do
41
+ optional :period_seconds,
42
+ type: Integer,
43
+ values: Webhookdb::SyncTarget.valid_period(1),
44
+ prompt: {
45
+ message: "How many seconds between syncs (#{default_period.begin} to #{default_period.end}):",
46
+ disable: ->(req) { req.path.end_with?("/update") },
47
+ }
48
+ is_db && optional(:schema,
49
+ type: String,
50
+ db_identifier: true,
51
+ allow_blank: true,
52
+ desc: "Schema (or namespace) to write the table into. Default to no schema/namespace.",)
53
+ # The description here says there is a default value, but the default value isn't actually saved to the SyncTarget
54
+ # object--it's inferred in the SyncTarget sync behavior when the table value is a blank string.
55
+ is_db && optional(:table,
56
+ type: String,
57
+ db_identifier: true,
58
+ allow_blank: true,
59
+ desc: "Table to create and update. Default to match the table name of the service integration.",)
60
+ !is_db && optional(:page_size,
61
+ type: Integer,
62
+ values: (1..1000),
63
+ desc: "Maximum number of rows in each POST request.",)
64
+ end
65
+ params :connection_url do
66
+ optional :connection_url,
67
+ type: String,
68
+ prompt: "Enter the #{is_db ? 'database connection string' : 'HTTP endpoint'} that WebhookDB should sync data to:",
69
+ connection_url_type: is_db ? "db" : "http"
70
+ end
71
+
72
+ def entity
73
+ return db? ? Webhookdb::API::DbSyncTargetEntity : Webhookdb::API::HttpSyncTargetEntity
74
+ end
75
+
76
+ def validate_period!(org, value)
77
+ r = Webhookdb::SyncTarget.valid_period_for(org)
78
+ return if r.cover?(value)
79
+ err = "The valid sync period for organization #{org.name} is between #{r.begin} and #{r.end} seconds."
80
+ invalid!(err, message: err)
81
+ end
82
+
83
+ def verify_connection(url)
84
+ if db?
85
+ Webhookdb::SyncTarget.verify_db_connection(url)
86
+ else
87
+ Webhookdb::SyncTarget.verify_http_connection(url)
88
+ end
89
+ rescue Webhookdb::SyncTarget::InvalidConnection => e
90
+ merror!(400, e.to_s, code: "invalid_sync_target_connection")
91
+ end
92
+
93
+ def predicate = db? ? :db? : :http?
94
+ def fullname = db? ? "database" : "http"
95
+ def cmd = db? ? "dbsync" : "httpsync"
96
+ def tgtname = db? ? "database" : "endpoint"
97
+ end
98
+
99
+ route_setting :target_type, target_type_resource
100
+ get do
101
+ org = lookup_org!
102
+ subs = org.all_sync_targets.filter(&predicate)
103
+ message = ""
104
+ if subs.empty?
105
+ message = "Organization #{org.name} has no #{fullname} sync targets set up.\n" \
106
+ "Use `webhookdb #{cmd} create` to synchronization WebhookDB tables to your #{tgtname}."
107
+ end
108
+ status 200
109
+ present_collection subs, with: entity, message:
110
+ end
111
+
112
+ params do
113
+ use :connection_url
114
+ use :sync_target_params
115
+ optional :service_integration_opaque_id,
116
+ type: String, allow_blank: false,
117
+ desc: "This is a deprecated parameter. In the future, please use `service_integration_identifier`."
118
+ optional :service_integration_identifier, type: String, allow_blank: false
119
+ at_least_one_of :service_integration_opaque_id, :service_integration_identifier
120
+ end
121
+ route_setting :target_type, target_type_resource
122
+ post :create do
123
+ customer = current_customer
124
+ org = lookup_org!(customer:)
125
+ ensure_admin!(org, customer:)
126
+ identifier = params[:service_integration_identifier] || params[:service_integration_opaque_id]
127
+ sint = lookup_service_integration!(org, identifier)
128
+
129
+ validate_period!(sint.organization, params[:period_seconds])
130
+ verify_connection(params[:connection_url])
131
+ stgt = Webhookdb::SyncTarget.create(
132
+ service_integration: sint,
133
+ connection_url: params[:connection_url],
134
+ period_seconds: params[:period_seconds],
135
+ schema: params[:schema] || "",
136
+ table: params[:table] || "",
137
+ page_size: params[:page_size],
138
+ created_by: customer,
139
+ )
140
+ message = "Every #{stgt.period_seconds} seconds, data from #{sint.service_name} " \
141
+ "in #{sint.table_name} will be synchronized to #{stgt.displaysafe_connection_url}"
142
+ status 200
143
+ present stgt, with: entity, message:
144
+ rescue Grape::Exceptions::Validation => e
145
+ merror!(400, e.message)
146
+ end
147
+
148
+ route_param :opaque_id do
149
+ helpers do
150
+ def lookup!
151
+ org = lookup_org!
152
+ ensure_admin!(org)
153
+ (stgt = org.all_sync_targets_dataset[Sequel[:sync_targets][:opaque_id] => params[:opaque_id]])
154
+ merror!(403, "There is no #{fullname} sync target with that id.") if
155
+ stgt.nil? || !stgt.send(predicate)
156
+ return stgt
157
+ end
158
+ end
159
+ params do
160
+ optional :user, type: String, prompt: "Username for the connection:"
161
+ optional :password, type: String, prompt: "Password for the connection:"
162
+ end
163
+ route_setting :target_type, target_type_resource
164
+ post :update_credentials do
165
+ stgt = lookup!
166
+ uri = URI(stgt.connection_url)
167
+ uri.user = params[:user]
168
+ uri.password = params[:password]
169
+ verify_connection(uri.to_s)
170
+ stgt.update(connection_url: uri.to_s)
171
+ status 200
172
+ present stgt, with: entity, message: "Connection URL has been updated."
173
+ end
174
+
175
+ params do
176
+ use :sync_target_params
177
+ end
178
+ route_setting :target_type, target_type_resource
179
+ post :update do
180
+ stgt = lookup!
181
+ stgt.table = params[:table] if params.key?(:table)
182
+ stgt.schema = params[:schema] if params.key?(:schema)
183
+ stgt.page_size = params[:page_size] if params.key?(:page_size)
184
+ if params.key?(:period_seconds) && (period_seconds = params[:period_seconds])
185
+ validate_period!(stgt.organization, period_seconds)
186
+ stgt.period_seconds = period_seconds
187
+ end
188
+ save_or_error!(stgt)
189
+ status 200
190
+ present stgt, with: entity, message: "#{fullname.capitalize} sync target has been updated."
191
+ end
192
+
193
+ params do
194
+ optional :confirm, type: String
195
+ end
196
+ route_setting :target_type, target_type_resource
197
+ post :delete do
198
+ stgt = lookup!
199
+ prompted_table = stgt.service_integration.table_name
200
+ if prompted_table != params[:confirm]&.strip
201
+ Webhookdb::API::Helpers.prompt_for_required_param!(
202
+ request,
203
+ :confirm,
204
+ "Please confirm your deletion by entering the table name that is being synced by " \
205
+ "this #{fullname} sync target (#{prompted_table}):",
206
+ )
207
+ end
208
+
209
+ stgt.logger.warn("destroying_sync_target", sync_target_id: stgt.id, customer_id: current_customer.id)
210
+ stgt.destroy
211
+ status 200
212
+ message = "#{fullname.capitalize} sync target has been removed and will no longer sync."
213
+ present stgt, with: entity, message:
214
+ end
215
+
216
+ route_setting :target_type, target_type_resource
217
+ post :sync do
218
+ stgt = lookup!
219
+ status 200
220
+ next_sync = stgt.next_possible_sync(now: Time.now)
221
+ Webhookdb::Jobs::SyncTargetRunSync.perform_at(next_sync, stgt.id)
222
+ message = "#{fullname.capitalize} sync has been scheduled. It should start at about #{next_sync}."
223
+ present stgt, with: entity, message:
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ # rubocop:enable Layout/LineLength
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grape"
4
+
5
+ require "webhookdb/api"
6
+ require "webhookdb/postgres"
7
+
8
+ # Health check and other metadata endpoints.
9
+ class Webhookdb::API::System < Webhookdb::Service
10
+ format :json
11
+
12
+ require "webhookdb/service/helpers"
13
+ helpers Webhookdb::Service::Helpers
14
+
15
+ get :healthz do
16
+ Webhookdb::Postgres::Model.db.execute("SELECT 1=1")
17
+ status 200
18
+ {o: "k"}
19
+ end
20
+
21
+ get :statusz do
22
+ status 200
23
+ {
24
+ env: Webhookdb::RACK_ENV,
25
+ version: Webhookdb::VERSION,
26
+ commit: Webhookdb::COMMIT,
27
+ release: Webhookdb::RELEASE,
28
+ log_level: Webhookdb.logger.level,
29
+ }
30
+ end
31
+
32
+ resource :debug do
33
+ get :echo do
34
+ pp params.to_h
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/api"
4
+
5
+ class Webhookdb::API::WebhookSubscriptions < Webhookdb::API::V1
6
+ resource :organizations do
7
+ route_param :org_identifier, type: String do
8
+ resource :webhook_subscriptions do
9
+ desc "Return all webhook subscriptions for the given org, and all integrations."
10
+ get do
11
+ org = lookup_org!
12
+ subs = org.all_webhook_subscriptions
13
+ message = ""
14
+ if subs.empty?
15
+ message = "Organization #{org.name} has no webhook subscriptions set up.\n" \
16
+ "Use `webhookdb webhooks create` to set one up."
17
+ end
18
+ status 200
19
+ present_collection subs, with: Webhookdb::API::WebhookSubscriptionEntity, message:
20
+ end
21
+
22
+ params do
23
+ optional :url, prompt: "Enter the URL that WebhookDB should POST webhooks to:"
24
+ optional :webhook_secret, prompt: "Enter a random secret used to sign and verify webhooks to the given url:"
25
+ optional :service_integration_identifier,
26
+ type: String,
27
+ desc: "If provided, attach the webhook subscription to this integration rather than the org."
28
+ optional :service_integration_opaque_id,
29
+ type: String,
30
+ desc: "This is a deprecated parameter. In the future, please use `service_integration_identifier`."
31
+ end
32
+ post :create do
33
+ org = lookup_org!
34
+ sint = nil
35
+ identifier = params[:service_integration_identifier] || params[:service_integration_opaque_id]
36
+ sint = lookup_service_integration!(org, identifier) if identifier.present?
37
+ webhook_sub = Webhookdb::WebhookSubscription.create(
38
+ webhook_secret: params[:webhook_secret],
39
+ deliver_to_url: params[:url],
40
+ organization: sint ? nil : org,
41
+ service_integration: sint,
42
+ created_by: current_customer,
43
+ )
44
+ message = if sint
45
+ "All webhooks for this #{sint.service_name} integration will be sent to #{params[:url]}"
46
+ else
47
+ "All webhooks for all integrations belonging to organization #{org.name} will be sent to #{params[:url]}."
48
+ end
49
+ status 200
50
+ present webhook_sub, with: Webhookdb::API::WebhookSubscriptionEntity, message:
51
+ end
52
+
53
+ route_param :opaque_id, type: String do
54
+ helpers do
55
+ def lookup_sub!
56
+ org = lookup_org!
57
+ whsub = org.all_webhook_subscriptions_dataset[opaque_id: params[:opaque_id]]
58
+ merror!(403, "No webhook subscription with that ID exists in that organization.") if whsub.nil?
59
+ return whsub
60
+ end
61
+ end
62
+
63
+ post :test do
64
+ webhook_sub = lookup_sub!
65
+ webhook_sub.publish_immediate("test", webhook_sub.id)
66
+ message = "A test event has been sent to #{webhook_sub.deliver_to_url}."
67
+ status 200
68
+ present({}, with: Webhookdb::API::BaseEntity, message:)
69
+ end
70
+
71
+ post :delete do
72
+ webhook_sub = lookup_sub!
73
+ webhook_sub.delete
74
+ message = "Events will no longer be sent to #{webhook_sub.deliver_to_url}."
75
+ status 200
76
+ present({}, with: Webhookdb::API::BaseEntity, message:)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ resource :webhook_subscriptions do
84
+ post :create do
85
+ endpoint_removed!
86
+ end
87
+ route_param :arg do
88
+ post :test do
89
+ endpoint_removed!
90
+ end
91
+ post :delete do
92
+ endpoint_removed!
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grape"
4
+
5
+ require "webhookdb"
6
+ require "webhookdb/service"
7
+
8
+ # API is the namespace module for API resources.
9
+ module Webhookdb::API
10
+ require "webhookdb/api/entities"
11
+
12
+ class V1 < Webhookdb::Service
13
+ def self.inherited(subclass)
14
+ super
15
+ subclass.instance_eval do
16
+ version "v1", using: :path
17
+ format :json
18
+
19
+ require "webhookdb/service/helpers"
20
+ helpers Webhookdb::Service::Helpers
21
+ require "webhookdb/api/helpers"
22
+ helpers Webhookdb::API::Helpers
23
+ require "webhookdb/api/connstr_auth"
24
+
25
+ helpers do
26
+ def verified_customer!
27
+ c = current_customer
28
+ forbidden! unless c.phone_verified?
29
+ return c
30
+ end
31
+
32
+ # Lookup the organization.
33
+ # @param identifier [String] Can be passed in, or is extracted from the route params
34
+ # (:org_identifier, or :org if :org_identiier is '-').
35
+ # @param customer [Webhookdb::Customer] Authed customer. Will use current_customer if nil.
36
+ # @param allow_connstr_auth [Boolean] True to use Webhookdb::API::ConnstrAuth.
37
+ # See module for more details.
38
+ def lookup_org!(identifier=nil, customer: nil, allow_connstr_auth: false)
39
+ identifier ||= params[:org_identifier]
40
+ if identifier == "-"
41
+ identifier = params[:org]
42
+ merror!(400, "must supply 'org_identifier' or 'org' param", code: "missing_org") unless identifier
43
+ end
44
+ # Run this first to verify authentication before other lookups.
45
+ customer ||= allow_connstr_auth ? current_customer? : current_customer
46
+ # Can return multiple orgs, including ones the user cannot access
47
+ orgs = Webhookdb::Organization.with_identifier(identifier).all
48
+ merror!(403, "There is no organization with that identifier.") if orgs.empty?
49
+ if customer
50
+ # This is scoped to just orgs the user can access. We check if the identifier
51
+ # matches multiple orgs, in which case it's ambiguous.
52
+ memberships = customer.verified_memberships_dataset.where(organization: orgs).limit(2).all
53
+ permission_error!("You don't have permissions with that organization.") if memberships.empty?
54
+ merror!(500, "ambiguous", alert: true) if memberships.size > 1 # TODO: better message, tests
55
+ return memberships.first.organization
56
+ end
57
+ raise "something went wrong" unless allow_connstr_auth
58
+ org = Webhookdb::API::ConnstrAuth.find_authed(orgs, request)
59
+ unauthenticated! if org.nil?
60
+ return org
61
+ end
62
+
63
+ def ensure_admin!(org=nil, customer: nil)
64
+ customer ||= current_customer
65
+ org ||= lookup_org!
66
+ has_no_admin = org.verified_memberships_dataset.
67
+ where(customer:, membership_role: Webhookdb::Role.admin_role).
68
+ empty?
69
+ permission_error!("You don't have admin privileges with #{org.name}.") if has_no_admin
70
+ end
71
+ end
72
+
73
+ before do
74
+ Sentry.configure_scope do |scope|
75
+ scope.set_tags(application: "public-api")
76
+ end
77
+ end
78
+
79
+ before_validation do
80
+ # We want to strip control characters out of the string inputs
81
+ # Found the control character regex here:
82
+ # https://www.appsloveworld.com/ruby/100/167/how-to-remove-control-characters-in-ruby
83
+ #
84
+ rgx = /\e\[[^\x40-\x7E]*[\x40-\x7E]/
85
+ self.params.each do |k, v|
86
+ params[k] = v.gsub(rgx, "") if v.is_a?(String)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grape"
4
+ require "grape-swagger"
5
+ require "sidekiq/web"
6
+ require "sidekiq/cron/web"
7
+
8
+ require "webhookdb/api"
9
+ require "webhookdb/async"
10
+ require "webhookdb/sentry"
11
+ require "webhookdb/service"
12
+
13
+ require "webhookdb/api/auth"
14
+ require "webhookdb/api/db"
15
+ require "webhookdb/api/demo"
16
+ require "webhookdb/api/install"
17
+ require "webhookdb/api/me"
18
+ require "webhookdb/api/organizations"
19
+ require "webhookdb/api/replay"
20
+ require "webhookdb/api/service_integrations"
21
+ require "webhookdb/api/services"
22
+ require "webhookdb/api/stripe"
23
+ require "webhookdb/api/subscriptions"
24
+ require "webhookdb/api/sync_targets"
25
+ require "webhookdb/api/system"
26
+ require "webhookdb/api/webhook_subscriptions"
27
+
28
+ require "webhookdb/admin_api/auth"
29
+ require "webhookdb/admin_api/customers"
30
+ require "webhookdb/admin_api/database_documents"
31
+ require "webhookdb/admin_api/message_deliveries"
32
+ require "webhookdb/admin_api/roles"
33
+
34
+ require "webterm/apps"
35
+
36
+ module Webhookdb::Apps
37
+ class API < Webhookdb::Service
38
+ mount Webhookdb::API::Auth
39
+ mount Webhookdb::API::Db
40
+ mount Webhookdb::API::Demo
41
+ mount Webhookdb::API::Install
42
+ mount Webhookdb::API::Me
43
+ mount Webhookdb::API::Organizations
44
+ mount Webhookdb::API::Replay
45
+ mount Webhookdb::API::ServiceIntegrations
46
+ mount Webhookdb::API::Services
47
+ mount Webhookdb::API::Stripe
48
+ mount Webhookdb::API::Subscriptions
49
+ mount Webhookdb::API::SyncTargets
50
+ mount Webhookdb::API::System
51
+ mount Webhookdb::API::WebhookSubscriptions
52
+ add_swagger_documentation if ENV["RACK_ENV"] == "development"
53
+ end
54
+
55
+ class AdminAPI < Webhookdb::Service
56
+ mount Webhookdb::AdminAPI::Auth
57
+ mount Webhookdb::AdminAPI::DatabaseDocuments
58
+ mount Webhookdb::AdminAPI::MessageDeliveries
59
+ mount Webhookdb::AdminAPI::Roles
60
+ mount Webhookdb::AdminAPI::Customers
61
+ add_swagger_documentation if ENV["RACK_ENV"] == "development"
62
+ end
63
+
64
+ SidekiqWeb = Rack::Builder.new do
65
+ use Sentry::Rack::CaptureExceptions if Webhookdb::Sentry.enabled?
66
+ use Rack::Auth::Basic, "Protected Area" do |username, password|
67
+ # Protect against timing attacks: (https://codahale.com/a-lesson-in-timing-attacks/)
68
+ # - Use & (do not use &&) so that it doesn't short circuit.
69
+ # - Use digests to stop length information leaking
70
+ Rack::Utils.secure_compare(
71
+ ::Digest::SHA256.hexdigest(username),
72
+ ::Digest::SHA256.hexdigest(Webhookdb::Async.web_username),
73
+ ) & Rack::Utils.secure_compare(
74
+ ::Digest::SHA256.hexdigest(password),
75
+ ::Digest::SHA256.hexdigest(Webhookdb::Async.web_password),
76
+ )
77
+ end
78
+ use Rack::Session::Cookie, secret: Webhookdb::Service.session_secret, same_site: true, max_age: 86_400
79
+ run Sidekiq::Web
80
+ end
81
+
82
+ Webterm = Rack::Builder.new do
83
+ use(Rack::SslEnforcer, {redirect_html: false}) if Webhookdb::Webterm.enforce_ssl
84
+ use Rack::Deflater
85
+ use Rack::ConditionalGet
86
+ use Rack::ETag
87
+ map "/" do
88
+ use Webhookdb::Webterm::RedirectIndexHtmlToRoot
89
+ use Webhookdb::Webterm::ServeIndexHtmlFromRoot
90
+ run Webhookdb::Webterm::Files
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "amigo/audit_logger"
4
+ require "appydays/loggable"
5
+
6
+ class Webhookdb::Async::AuditLogger < Amigo::AuditLogger
7
+ include Appydays::Loggable
8
+
9
+ sidekiq_options queue: "critical"
10
+
11
+ MAX_STR_LEN = 64
12
+ STR_PREFIX_LEN = 12
13
+
14
+ def perform(event_json)
15
+ j2 = event_json.dup
16
+ j2["payload"] = self.trim_long_strings(j2["payload"], max_str_len: MAX_STR_LEN, str_prefix_len: STR_PREFIX_LEN)
17
+ super(j2)
18
+ end
19
+
20
+ def trim_long_strings(v, max_str_len:, str_prefix_len:)
21
+ case v
22
+ when Hash
23
+ v.transform_values do |hv|
24
+ self.trim_long_strings(hv, max_str_len:, str_prefix_len:)
25
+ end
26
+ when Array
27
+ v.map { |item| self.trim_long_strings(item, max_str_len:, str_prefix_len:) }
28
+ when String
29
+ if v.size > max_str_len
30
+ v[..str_prefix_len] + "..."
31
+ else
32
+ v
33
+ end
34
+ else
35
+ v
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "amigo/autoscaler"
4
+ require "amigo/autoscaler/heroku"
5
+ require "webhookdb/async"
6
+ require "webhookdb/heroku"
7
+
8
+ module Webhookdb::Async::Autoscaler
9
+ include Appydays::Configurable
10
+ include Appydays::Loggable
11
+
12
+ AVAILABLE_PROVIDERS = ["heroku"].freeze
13
+
14
+ def self._check_provider!
15
+ return if AVAILABLE_PROVIDERS.include?(self.provider)
16
+ return if !self.enabled && self.provider.blank?
17
+ raise "invalid AUTOSCALER_PROVIDER: '#{self.provider}', one of: #{AVAILABLE_PROVIDERS.join(', ')}"
18
+ end
19
+
20
+ configurable(:autoscaler) do
21
+ setting :enabled, false
22
+ setting :provider, ""
23
+ setting :latency_threshold, 10
24
+ setting :alert_interval, 180
25
+ setting :poll_interval, 30
26
+ setting :max_additional_workers, 2
27
+ setting :latency_restored_threshold, 0
28
+ setting :hostname_regex, /^web\.1$/, convert: ->(s) { Regexp.new(s) }
29
+
30
+ after_configured do
31
+ self._check_provider!
32
+ end
33
+ end
34
+
35
+ class << self
36
+ def enabled? = self.enabled
37
+
38
+ def start
39
+ raise "already started" unless @instance.nil?
40
+ case self.provider
41
+ when "heroku"
42
+ opts = {heroku: Webhookdb::Heroku.client, max_additional_workers: self.max_additional_workers}
43
+ (opts[:app_id_or_app_name] = self.heroku_app_id_or_app_name) if
44
+ self.heroku_app_id_or_app_name
45
+ (opts[:formation_id_or_formation_type] = self.heroku_formation_id_or_formation_type) if
46
+ self.heroku_formation_id_or_formation_type
47
+ @impl = Amigo::Autoscaler::Heroku.new(**opts)
48
+ else
49
+ self._check_provider!
50
+ end
51
+ @instance = Amigo::Autoscaler.new(
52
+ poll_interval: self.poll_interval,
53
+ latency_threshold: self.latency_threshold,
54
+ hostname_regex: self.hostname_regex,
55
+ handlers: [self.method(:scale_up)],
56
+ alert_interval: self.alert_interval,
57
+ latency_restored_threshold: self.latency_restored_threshold,
58
+ latency_restored_handlers: [self.method(:scale_down)],
59
+ log: ->(level, msg, kw={}) { self.logger.send(level, msg, kw) },
60
+ )
61
+ return @instance.start
62
+ end
63
+
64
+ def stop
65
+ raise "not started" if @instance.nil?
66
+ @instance.stop
67
+ end
68
+
69
+ def scale_up(names_and_latencies, depth:, duration:, **)
70
+ scale_action = @impl.scale_up(names_and_latencies, depth:, duration:, **)
71
+ kw = {queues: names_and_latencies, depth:, duration:, scale_action:}
72
+ self.logger.warn("high_latency_queues_event", **kw)
73
+ Sentry.with_scope do |scope|
74
+ scope&.set_extras(**kw)
75
+ Sentry.capture_message("Some queues have a high latency")
76
+ end
77
+ end
78
+
79
+ def scale_down(depth:, duration:, **)
80
+ scale_action = @impl.scale_down(depth:, duration:, **)
81
+ self.logger.warn("high_latency_queues_resolved", depth:, duration:, scale_action:)
82
+ end
83
+ end
84
+ end