webhookdb 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/admin-dist/assets/index-6aebf805.js +264 -0
  3. data/admin-dist/favicon.ico +0 -0
  4. data/admin-dist/index.html +130 -0
  5. data/admin-dist/manifest.json +15 -0
  6. data/data/messages/replicators/url-recorder.liquid +20 -0
  7. data/data/messages/templates/errors/signalwire_send_sms.email.liquid +31 -0
  8. data/data/messages/web/install-customer-login.liquid +6 -5
  9. data/data/messages/web/install-error.liquid +1 -1
  10. data/data/messages/web/install-forbidden.liquid +25 -0
  11. data/data/messages/web/install-org-chooser.liquid +40 -0
  12. data/data/messages/web/install-success.liquid +2 -1
  13. data/data/messages/web/install.liquid +2 -1
  14. data/data/messages/web/partials/head.liquid +2 -0
  15. data/data/messages/web/styles.liquid +24 -0
  16. data/db/migrations/040_saved_query_fix_unique.rb +17 -0
  17. data/db/migrations/041_views.rb +20 -0
  18. data/db/migrations/042_sint_lock.rb +10 -0
  19. data/db/migrations/043_text_search.rb +28 -0
  20. data/db/migrations/044_oauth_session_token_cache.rb +21 -0
  21. data/integration/auth_spec.rb +2 -2
  22. data/lib/sequel/plugins/text_searchable.rb +165 -0
  23. data/lib/sequel/text_searchable.rb +42 -0
  24. data/lib/webhookdb/admin_api/auth.rb +24 -3
  25. data/lib/webhookdb/admin_api/data_provider.rb +196 -0
  26. data/lib/webhookdb/admin_api/entities.rb +143 -28
  27. data/lib/webhookdb/admin_api.rb +0 -2
  28. data/lib/webhookdb/api/auth.rb +5 -6
  29. data/lib/webhookdb/api/db.rb +31 -6
  30. data/lib/webhookdb/api/entities.rb +7 -1
  31. data/lib/webhookdb/api/helpers.rb +6 -25
  32. data/lib/webhookdb/api/install.rb +204 -79
  33. data/lib/webhookdb/api/organizations.rb +14 -12
  34. data/lib/webhookdb/api/saved_queries.rb +10 -3
  35. data/lib/webhookdb/api/saved_views.rb +99 -0
  36. data/lib/webhookdb/api/service_integrations.rb +15 -9
  37. data/lib/webhookdb/api/subscriptions.rb +3 -1
  38. data/lib/webhookdb/api/sync_targets.rb +9 -7
  39. data/lib/webhookdb/api/system.rb +1 -0
  40. data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
  41. data/lib/webhookdb/apps.rb +30 -7
  42. data/lib/webhookdb/async/audit_logger.rb +2 -0
  43. data/lib/webhookdb/async.rb +5 -0
  44. data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
  45. data/lib/webhookdb/backfill_job.rb +9 -0
  46. data/lib/webhookdb/customer.rb +5 -0
  47. data/lib/webhookdb/database_document.rb +1 -1
  48. data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
  49. data/lib/webhookdb/db_adapter.rb +20 -4
  50. data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
  51. data/lib/webhookdb/fixtures/organizations.rb +5 -0
  52. data/lib/webhookdb/fixtures/roles.rb +14 -0
  53. data/lib/webhookdb/fixtures/saved_views.rb +25 -0
  54. data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
  55. data/lib/webhookdb/http.rb +8 -2
  56. data/lib/webhookdb/icalendar.rb +3 -0
  57. data/lib/webhookdb/idempotency.rb +69 -22
  58. data/lib/webhookdb/increase.rb +69 -21
  59. data/lib/webhookdb/intercom.rb +10 -3
  60. data/lib/webhookdb/jobs/backfill.rb +3 -1
  61. data/lib/webhookdb/jobs/emailer.rb +0 -1
  62. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
  63. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
  64. data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
  65. data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
  66. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
  67. data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
  68. data/lib/webhookdb/message/body.rb +6 -4
  69. data/lib/webhookdb/message/delivery.rb +2 -0
  70. data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
  71. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
  72. data/lib/webhookdb/oauth/fake_provider.rb +44 -0
  73. data/lib/webhookdb/oauth/front_provider.rb +1 -2
  74. data/lib/webhookdb/oauth/increase_provider.rb +80 -0
  75. data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
  76. data/lib/webhookdb/oauth/session.rb +20 -0
  77. data/lib/webhookdb/oauth.rb +7 -21
  78. data/lib/webhookdb/organization/alerting.rb +2 -0
  79. data/lib/webhookdb/organization/database_migration.rb +3 -0
  80. data/lib/webhookdb/organization.rb +37 -6
  81. data/lib/webhookdb/organization_membership.rb +14 -7
  82. data/lib/webhookdb/postgres.rb +2 -0
  83. data/lib/webhookdb/replicator/base.rb +1 -0
  84. data/lib/webhookdb/replicator/docgen.rb +9 -1
  85. data/lib/webhookdb/replicator/fake.rb +2 -3
  86. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
  87. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
  88. data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
  89. data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
  90. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
  91. data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
  92. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
  93. data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
  94. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
  95. data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
  96. data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
  97. data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
  98. data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
  99. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
  100. data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
  101. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
  102. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
  103. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
  104. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
  105. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  106. data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
  107. data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
  108. data/lib/webhookdb/replicator/webhook_request.rb +4 -0
  109. data/lib/webhookdb/replicator.rb +8 -0
  110. data/lib/webhookdb/role.rb +5 -2
  111. data/lib/webhookdb/saved_query.rb +23 -0
  112. data/lib/webhookdb/saved_view.rb +73 -0
  113. data/lib/webhookdb/sentry.rb +2 -0
  114. data/lib/webhookdb/service/entities.rb +0 -4
  115. data/lib/webhookdb/service/helpers.rb +5 -0
  116. data/lib/webhookdb/service/middleware.rb +17 -0
  117. data/lib/webhookdb/service/types.rb +10 -8
  118. data/lib/webhookdb/service/validators.rb +1 -2
  119. data/lib/webhookdb/service/view_api.rb +1 -1
  120. data/lib/webhookdb/service_integration.rb +17 -15
  121. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
  122. data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
  123. data/lib/webhookdb/subscription.rb +2 -0
  124. data/lib/webhookdb/sync_target.rb +10 -2
  125. data/lib/webhookdb/tasks/message.rb +3 -1
  126. data/lib/webhookdb/version.rb +1 -1
  127. data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
  128. data/lib/webhookdb/webhook_subscription.rb +2 -0
  129. metadata +58 -9
  130. data/lib/webhookdb/admin_api/customers.rb +0 -63
  131. data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
  132. data/lib/webhookdb/admin_api/roles.rb +0 -15
@@ -65,7 +65,7 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
65
65
  ruby: lambda do |entry, **|
66
66
  self.entry_to_date(entry) if entry.is_a?(Hash) && self.entry_is_date_str?(entry)
67
67
  end,
68
- sql: ->(_) { raise NotImplementedError },
68
+ sql: Webhookdb::Replicator::Column::NOT_IMPLEMENTED,
69
69
  )
70
70
  CONV_DATETIME = Webhookdb::Replicator::Column::IsomorphicProc.new(
71
71
  ruby: lambda do |entry, **|
@@ -108,6 +108,8 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
108
108
  sql: ->(_) { raise NotImplementedError },
109
109
  )
110
110
 
111
+ def _webhook_response(_request) = Webhookdb::WebhookResponse.ok
112
+
111
113
  def _remote_key_column
112
114
  return Webhookdb::Replicator::Column.new(
113
115
  :compound_identity,
@@ -126,7 +128,7 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
126
128
  return [
127
129
  col.new(:calendar_external_id, TEXT, index: true),
128
130
  col.new(:uid, TEXT, data_key: ["UID", "v"], index: true),
129
- col.new(:row_updated_at, TIMESTAMP, index: true, defaulter: :now, optional: true),
131
+ col.new(:row_updated_at, TIMESTAMP, index: true),
130
132
  col.new(:last_modified_at,
131
133
  TIMESTAMP,
132
134
  index: true,
@@ -153,6 +155,8 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
153
155
  ]
154
156
  end
155
157
 
158
+ def _timestamp_column_name = :last_modified_at
159
+
156
160
  def _resource_and_event(request)
157
161
  return request.body, nil
158
162
  end
@@ -165,6 +169,41 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
165
169
  return data
166
170
  end
167
171
 
172
+ def _prepare_for_insert(resource, event, request, enrichment)
173
+ h = super
174
+ # Events can have a DTSTART, but no DTEND.
175
+ # https://icalendar.org/iCalendar-RFC-5545/3-6-1-event-component.html
176
+ # In these cases, we need to:
177
+ # - Use the duration, given.
178
+ # - Dates default to the next day.
179
+ # - Times default to start time.
180
+ if (_implicit_end_time = h[:start_at] && !h[:end_at])
181
+ self._set_implicit_end_at(resource, h)
182
+ elsif (_implicit_end_date = h[:start_date] && !h[:end_date])
183
+ self._set_implicit_end_date(resource, h)
184
+ end
185
+ return h
186
+ end
187
+
188
+ def _set_implicit_end_date(resource, h)
189
+ if (d = resource["DURATION"])
190
+ # See https://icalendar.org/iCalendar-RFC-5545/3-3-6-duration.html
191
+ dur = ActiveSupport::Duration.parse(d.fetch("v"))
192
+ h[:end_date] = h[:start_date] + dur
193
+ return
194
+ end
195
+ h[:end_date] = h[:start_date] + 1.day
196
+ end
197
+
198
+ def _set_implicit_end_at(resource, h)
199
+ if (d = resource["DURATION"])
200
+ dur = ActiveSupport::Duration.parse(d.fetch("v"))
201
+ h[:end_at] = h[:start_at] + dur
202
+ return
203
+ end
204
+ h[:end_at] = h[:start_at]
205
+ end
206
+
168
207
  # @return [Array<Webhookdb::Replicator::IndexSpec>]
169
208
  def _extra_index_specs
170
209
  return [
@@ -220,6 +259,21 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
220
259
  value.gsub!("\\r\\n", "\r\n")
221
260
  value.gsub!("\\n", "\n")
222
261
  value.gsub!("\\t", "\t")
262
+ # This line is not tested, since replicating issues with HTTP body encoding
263
+ # is really tricky (while I love Ruby's unicode handling, trying to replicate
264
+ # invalid data from other sources is a pain).
265
+ # However we do get invalid unicode sequences, like:
266
+ # DESCRIPTION:\r\nNFL regional registration opens 9/25 and ends 11/20\XAOwww.nflflag.com
267
+ # which cannot be encoded in JSON:
268
+ # Invalid Unicode [a0 77 77 77 2e] at 52 (JSON::GeneratorError)
269
+ # The only way I can think to handle this is with replacing invalid utf-8 chars
270
+ # (with the unicode questionmark icon), so they can be represented as JSON.
271
+ # This fix is here, and not in `Base#_to_json` (like the null char fixes),
272
+ # since I think this is an issue with feeds like icalendar,
273
+ # and not something to handle generally
274
+ # (we may also see this in something like Atom, but perhaps because
275
+ # atom is XML, not a 'plain' text format, it'll be more rare).
276
+ value.encode!("UTF-8", invalid: :replace, undef: :replace)
223
277
  end
224
278
  entry = {"v" => value}
225
279
  entry.merge!(params)
@@ -311,6 +365,54 @@ class Webhookdb::Replicator::IcalendarEventV1 < Webhookdb::Replicator::Base
311
365
  return
312
366
  end
313
367
 
368
+ # Delete CANCELLED events last updated (+row_updated_at+)' in the window between
369
+ # +stale_at+ to +age_cutoff+. This avoids endlessly adding to the icalendar events table
370
+ # due to feeds that change UIDs each fetch- events with changed UIDs will become CANCELLED,
371
+ # and then deleted over time.
372
+ # @param stale_at [Time] When an event is considered 'stale'.
373
+ # If stale events are a big problem, this can be shortened to just a few days.
374
+ # @param age_cutoff [Time] Where to stop searching for old events.
375
+ # This is important to avoid a full table scale when deleting events,
376
+ # since otherwise it is like 'row_updated_at < 35.days.ago'.
377
+ # Since this routine should run regularly, we should rarely have events more than 35 or 36 days old,
378
+ # for example.
379
+ # Use +nil+ to use no limit (a full table scan) which may be necessary when running this feature
380
+ # for the first time.
381
+ # @param chunk_size [Integer] The row delete is done in chunks to avoid long locks.
382
+ # The default seems safe, but it's exposed as a parameter if you need to play around with it,
383
+ # and can be done via configuration if needed at some point.
384
+ def delete_stale_cancelled_events(
385
+ stale_at: Webhookdb::Icalendar.stale_cancelled_event_threshold_days.days.ago,
386
+ age_cutoff: (Webhookdb::Icalendar.stale_cancelled_event_threshold_days + 10).days.ago,
387
+ chunk_size: 10_000
388
+ )
389
+ # Delete in chunks, like:
390
+ # DELETE from "public"."icalendar_event_v1_aaaa"
391
+ # WHERE pk IN (
392
+ # SELECT pk FROM "public"."icalendar_event_v1_aaaa"
393
+ # WHERE row_updated_at < (now() - '35 days'::interval)
394
+ # LIMIT 10000
395
+ # )
396
+ age = age_cutoff..stale_at
397
+ self.admin_dataset do |ds|
398
+ chunk_ds = ds.where(row_updated_at: age, status: "CANCELLED").select(:pk).limit(chunk_size)
399
+ loop do
400
+ # Due to conflicts where a feed is being inserted while the delete is happening,
401
+ # this may raise an error like:
402
+ # deadlock detected
403
+ # DETAIL: Process 18352 waits for ShareLock on transaction 435085606; blocked by process 24191.
404
+ # Process 24191 waits for ShareLock on transaction 435085589; blocked by process 18352.
405
+ # HINT: See server log for query details.
406
+ # CONTEXT: while deleting tuple (2119119,3) in relation "icalendar_event_v1_aaaa"
407
+ # Unit testing this is very difficult though, and in practice it is rare,
408
+ # and normal Sidekiq job retries should be sufficient to handle this.
409
+ # So we don't explicitly handle deadlocks, but could if it becomes an issue.
410
+ deleted = ds.where(pk: chunk_ds).delete
411
+ break if deleted != chunk_size
412
+ end
413
+ end
414
+ end
415
+
314
416
  def calculate_webhook_state_machine
315
417
  if (step = self.calculate_dependency_state_machine_step(dependency_help: ""))
316
418
  return step
@@ -11,12 +11,12 @@ class Webhookdb::Replicator::IncreaseAccountNumberV1 < Webhookdb::Replicator::Ba
11
11
  def self.descriptor
12
12
  return Webhookdb::Replicator::Descriptor.new(
13
13
  name: "increase_account_number_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseAccountNumberV1.new(sint) },
14
+ ctor: self,
15
15
  feature_roles: [],
16
16
  resource_name_singular: "Increase Account Number",
17
- supports_webhooks: true,
17
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
18
  supports_backfill: true,
19
- api_docs_url: "https://icalendar.org/",
19
+ api_docs_url: "https://increase.com/documentation/api",
20
20
  )
21
21
  end
22
22
 
@@ -26,52 +26,15 @@ class Webhookdb::Replicator::IncreaseAccountNumberV1 < Webhookdb::Replicator::Ba
26
26
 
27
27
  def _denormalized_columns
28
28
  return [
29
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
30
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
29
31
  Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
30
32
  Webhookdb::Replicator::Column.new(:account_number, TEXT, index: true),
31
33
  Webhookdb::Replicator::Column.new(:name, TEXT),
32
34
  Webhookdb::Replicator::Column.new(:routing_number, TEXT, index: true),
33
- Webhookdb::Replicator::Column.new(
34
- :row_created_at,
35
- TIMESTAMP,
36
- data_key: "created_at",
37
- event_key: "created_at",
38
- optional: true,
39
- defaulter: :now,
40
- index: true,
41
- ),
42
- Webhookdb::Replicator::Column.new(
43
- :row_updated_at,
44
- TIMESTAMP,
45
- data_key: "created_at",
46
- event_key: "created_at",
47
- defaulter: :now,
48
- optional: true,
49
- index: true,
50
- ),
51
35
  Webhookdb::Replicator::Column.new(:status, TEXT),
52
36
  ]
53
37
  end
54
38
 
55
- def _timestamp_column_name
56
- return :row_updated_at
57
- end
58
-
59
- def _update_where_expr
60
- return self.qualified_table_sequel_identifier[:row_updated_at] < Sequel[:excluded][:row_updated_at]
61
- end
62
-
63
- def _resource_and_event(request)
64
- return self._find_resource_and_event(request.body, "account_number")
65
- end
66
-
67
- def _upsert_update_expr(inserting, **_kwargs)
68
- update = super
69
- # Only set created_at if it's not set so the initial insert isn't modified.
70
- self._coalesce_excluded_on_update(update, [:row_created_at])
71
- return update
72
- end
73
-
74
- def _mixin_backfill_url
75
- return "#{self.service_integration.api_url}/account_numbers"
76
- end
39
+ def _mixin_object_type = "account_number"
77
40
  end
@@ -11,10 +11,10 @@ class Webhookdb::Replicator::IncreaseAccountTransferV1 < Webhookdb::Replicator::
11
11
  def self.descriptor
12
12
  return Webhookdb::Replicator::Descriptor.new(
13
13
  name: "increase_account_transfer_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseAccountTransferV1.new(sint) },
14
+ ctor: self,
15
15
  feature_roles: [],
16
16
  resource_name_singular: "Increase Account Transfer",
17
- supports_webhooks: true,
17
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
18
  supports_backfill: true,
19
19
  api_docs_url: "https://increase.com/documentation/api",
20
20
  )
@@ -28,34 +28,17 @@ class Webhookdb::Replicator::IncreaseAccountTransferV1 < Webhookdb::Replicator::
28
28
  return [
29
29
  Webhookdb::Replicator::Column.new(:amount, INTEGER, index: true),
30
30
  Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
31
- Webhookdb::Replicator::Column.new(:canceled_at, TIMESTAMP, data_key: ["cancellation", "canceled_at"],
32
- optional: true,),
33
31
  Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
32
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
33
+ Webhookdb::Replicator::Column.new(
34
+ :canceled_at, TIMESTAMP, data_key: ["cancellation", "canceled_at"], optional: true,
35
+ ),
34
36
  Webhookdb::Replicator::Column.new(:destination_account_id, TEXT, index: true),
35
37
  Webhookdb::Replicator::Column.new(:destination_transaction_id, TEXT, index: true),
36
38
  Webhookdb::Replicator::Column.new(:status, TEXT),
37
- Webhookdb::Replicator::Column.new(:template_id, TEXT),
38
39
  Webhookdb::Replicator::Column.new(:transaction_id, TEXT, index: true),
39
- Webhookdb::Replicator::Column.new(
40
- :updated_at,
41
- TIMESTAMP,
42
- data_key: "created_at",
43
- event_key: "created_at",
44
- defaulter: :now,
45
- index: true,
46
- ),
47
40
  ]
48
41
  end
49
42
 
50
- def _update_where_expr
51
- return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
52
- end
53
-
54
- def _resource_and_event(request)
55
- return self._find_resource_and_event(request.body, "account_transfer")
56
- end
57
-
58
- def _mixin_backfill_url
59
- return "#{self.service_integration.api_url}/account_transfers"
60
- end
43
+ def _mixin_object_type = "account_transfer"
61
44
  end
@@ -11,10 +11,10 @@ class Webhookdb::Replicator::IncreaseAccountV1 < Webhookdb::Replicator::Base
11
11
  def self.descriptor
12
12
  return Webhookdb::Replicator::Descriptor.new(
13
13
  name: "increase_account_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseAccountV1.new(sint) },
14
+ ctor: self,
15
15
  feature_roles: [],
16
16
  resource_name_singular: "Increase Account",
17
- supports_webhooks: true,
17
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
18
  supports_backfill: true,
19
19
  api_docs_url: "https://increase.com/documentation/api",
20
20
  )
@@ -26,38 +26,14 @@ class Webhookdb::Replicator::IncreaseAccountV1 < Webhookdb::Replicator::Base
26
26
 
27
27
  def _denormalized_columns
28
28
  return [
29
- Webhookdb::Replicator::Column.new(:balance, INTEGER, index: true),
30
- Webhookdb::Replicator::Column.new(
31
- :created_at,
32
- TIMESTAMP,
33
- data_key: "created_at",
34
- defaulter: :now,
35
- index: true,
36
- ),
37
- Webhookdb::Replicator::Column.new(:entity_id, TEXT, index: true),
38
- Webhookdb::Replicator::Column.new(:interest_accrued, DECIMAL),
29
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
30
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
39
31
  Webhookdb::Replicator::Column.new(:name, TEXT),
32
+ Webhookdb::Replicator::Column.new(:entity_id, TEXT, index: true),
40
33
  Webhookdb::Replicator::Column.new(:status, TEXT),
41
- Webhookdb::Replicator::Column.new(
42
- :updated_at,
43
- TIMESTAMP,
44
- data_key: "created_at",
45
- event_key: "created_at",
46
- defaulter: :now,
47
- index: true,
48
- ),
34
+ Webhookdb::Replicator::Column.new(:interest_accrued, DECIMAL),
49
35
  ]
50
36
  end
51
37
 
52
- def _update_where_expr
53
- return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
54
- end
55
-
56
- def _resource_and_event(request)
57
- return self._find_resource_and_event(request.body, "account")
58
- end
59
-
60
- def _mixin_backfill_url
61
- return "#{self.service_integration.api_url}/accounts"
62
- end
38
+ def _mixin_object_type = "account"
63
39
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "webhookdb/increase"
4
3
  require "webhookdb/replicator/increase_v1_mixin"
5
4
 
6
5
  class Webhookdb::Replicator::IncreaseACHTransferV1 < Webhookdb::Replicator::Base
@@ -11,10 +10,10 @@ class Webhookdb::Replicator::IncreaseACHTransferV1 < Webhookdb::Replicator::Base
11
10
  def self.descriptor
12
11
  return Webhookdb::Replicator::Descriptor.new(
13
12
  name: "increase_ach_transfer_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseACHTransferV1.new(sint) },
13
+ ctor: self,
15
14
  feature_roles: [],
16
15
  resource_name_singular: "Increase ACH Transfer",
17
- supports_webhooks: true,
16
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
17
  supports_backfill: true,
19
18
  api_docs_url: "https://increase.com/documentation/api",
20
19
  )
@@ -29,50 +28,13 @@ class Webhookdb::Replicator::IncreaseACHTransferV1 < Webhookdb::Replicator::Base
29
28
  Webhookdb::Replicator::Column.new(:account_number, TEXT, index: true),
30
29
  Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
31
30
  Webhookdb::Replicator::Column.new(:amount, INTEGER, index: true),
32
- Webhookdb::Replicator::Column.new(
33
- :created_at,
34
- TIMESTAMP,
35
- data_key: "created_at",
36
- index: true,
37
- ),
31
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
32
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
38
33
  Webhookdb::Replicator::Column.new(:routing_number, TEXT, index: true),
39
34
  Webhookdb::Replicator::Column.new(:status, TEXT),
40
35
  Webhookdb::Replicator::Column.new(:transaction_id, TEXT, index: true),
41
- Webhookdb::Replicator::Column.new(
42
- :updated_at,
43
- TIMESTAMP,
44
- data_key: "created_at",
45
- event_key: "created_at",
46
- defaulter: :now,
47
- index: true,
48
- ),
49
36
  ]
50
37
  end
51
38
 
52
- def _prepare_for_insert(resource, event, request, enrichment)
53
- # created_at is marked required, but to skip on nil.
54
- # This will preserve its existing value when we update the webhook.
55
- resource["created_at"] = nil if event&.fetch("event") == "updated"
56
- return super
57
- end
58
-
59
- def _upsert_update_expr(inserting, enrichment: nil)
60
- update = super
61
- update[:data] = Sequel.lit("#{self.service_integration.table_name}.data || excluded.data")
62
- return update
63
- end
64
-
65
- def _update_where_expr
66
- return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
67
- end
68
-
69
- def _resource_and_event(request)
70
- resource, event = self._find_resource_and_event(request.body, "ach_transfer")
71
- return nil, nil if (resource && resource["type"]) == "inbound_ach_transfer"
72
- return resource, event
73
- end
74
-
75
- def _mixin_backfill_url
76
- return "#{self.service_integration.api_url}/transfers/achs"
77
- end
39
+ def _mixin_object_type = "ach_transfer"
78
40
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Replicator::IncreaseAppV1 < Webhookdb::Replicator::Base
4
+ include Appydays::Loggable
5
+
6
+ # @return [Webhookdb::Replicator::Descriptor]
7
+ def self.descriptor
8
+ return Webhookdb::Replicator::Descriptor.new(
9
+ name: "increase_app_v1",
10
+ ctor: self,
11
+ feature_roles: [],
12
+ resource_name_singular: "Increase App",
13
+ resource_name_plural: "Increase App",
14
+ supports_webhooks: true,
15
+ supports_backfill: true,
16
+ install_url: "#{Webhookdb.api_url}/increase",
17
+ documentation_url: "https://docs.webhookdb.com/guides/increase/",
18
+ description: "Replicate your Increase data to WebhookDB Cloud in one click using " \
19
+ "our [WebhookDB-Increase integration](https://docs.webhookdb.com/guides/increase).",
20
+ )
21
+ end
22
+
23
+ def _remote_key_column
24
+ return Webhookdb::Replicator::Column.new(:ignore_id, INTEGER)
25
+ end
26
+
27
+ def _denormalized_columns
28
+ return []
29
+ end
30
+
31
+ def _upsert_webhook(request, **kw)
32
+ raise Webhookdb::InvalidPrecondition, "can only handle event payloads" unless request.body.fetch("type") == "event"
33
+ dispatchable = self.service_integration.dependents.select do |d|
34
+ d.service_name == "increase_event_v1" || d.replicator.handle_event?(request.body)
35
+ end
36
+ dispatchable.each do |sint|
37
+ sint.replicator.upsert_webhook(request, **kw)
38
+ end
39
+ return nil
40
+ end
41
+
42
+ def _fetch_backfill_page(*)
43
+ return [], nil
44
+ end
45
+
46
+ def webhook_response(request)
47
+ return Webhookdb::Increase.webhook_response(request, Webhookdb::Increase.webhook_secret)
48
+ end
49
+
50
+ def calculate_webhook_state_machine
51
+ return self.calculate_backfill_state_machine
52
+ end
53
+
54
+ def calculate_backfill_state_machine
55
+ step = Webhookdb::Replicator::StateMachineStep.new
56
+ step.output = %(This replicator is managed automatically using OAuth through Increase.
57
+ Head over to #{self.descriptor.install_url} to learn more.)
58
+ step.completed
59
+ return step
60
+ end
61
+
62
+ def build_dependents
63
+ org = self.service_integration.organization
64
+ parent_descr = self.descriptor
65
+ sints = self.service_integration.organization.available_replicators.
66
+ select { |dd| dd.dependency_descriptor == parent_descr }.
67
+ map do |dd|
68
+ Webhookdb::ServiceIntegration.create_disambiguated(
69
+ dd.name,
70
+ organization: org,
71
+ depends_on: self.service_integration,
72
+ )
73
+ end
74
+ sints.
75
+ select { |sint| sint.replicator.descriptor.supports_backfill? }.
76
+ each { |sint| sint.replicator._enqueue_backfill_jobs(incremental: false) }
77
+ end
78
+ end
@@ -11,10 +11,10 @@ class Webhookdb::Replicator::IncreaseCheckTransferV1 < Webhookdb::Replicator::Ba
11
11
  def self.descriptor
12
12
  return Webhookdb::Replicator::Descriptor.new(
13
13
  name: "increase_check_transfer_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseCheckTransferV1.new(sint) },
14
+ ctor: self,
15
15
  feature_roles: [],
16
16
  resource_name_singular: "Increase Check Transfer",
17
- supports_webhooks: true,
17
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
18
  supports_backfill: true,
19
19
  api_docs_url: "https://increase.com/documentation/api",
20
20
  )
@@ -26,39 +26,33 @@ class Webhookdb::Replicator::IncreaseCheckTransferV1 < Webhookdb::Replicator::Ba
26
26
 
27
27
  def _denormalized_columns
28
28
  return [
29
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
30
+ Webhookdb::Replicator::Column.new(:updated_at, TIMESTAMP, index: true),
29
31
  Webhookdb::Replicator::Column.new(:account_id, TEXT, index: true),
30
- Webhookdb::Replicator::Column.new(:address_line1, TEXT),
31
- Webhookdb::Replicator::Column.new(:address_city, TEXT),
32
- Webhookdb::Replicator::Column.new(:address_state, TEXT),
33
- Webhookdb::Replicator::Column.new(:address_zip, TEXT, index: true),
34
32
  Webhookdb::Replicator::Column.new(:amount, INTEGER, index: true),
35
- Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, data_key: "created_at", index: true),
36
- Webhookdb::Replicator::Column.new(:mailed_at, TIMESTAMP),
37
- Webhookdb::Replicator::Column.new(:recipient_name, TEXT),
33
+ Webhookdb::Replicator::Column.new(:account_number, TEXT, index: true),
34
+ Webhookdb::Replicator::Column.new(:routing_number, TEXT, index: true),
35
+ Webhookdb::Replicator::Column.new(:check_number, TEXT, index: true),
36
+ Webhookdb::Replicator::Column.new(
37
+ :recipient_name,
38
+ TEXT,
39
+ data_key: ["physical_check", "recipient_name"], optional: true,
40
+ ),
38
41
  Webhookdb::Replicator::Column.new(:status, TEXT),
39
- Webhookdb::Replicator::Column.new(:submitted_at, TIMESTAMP, index: true),
40
- Webhookdb::Replicator::Column.new(:template_id, TEXT),
41
- Webhookdb::Replicator::Column.new(:transaction_id, TEXT, index: true),
42
42
  Webhookdb::Replicator::Column.new(
43
- :updated_at,
44
- TIMESTAMP,
45
- data_key: "created_at",
46
- event_key: "created_at",
47
- defaulter: :now,
48
- index: true,
43
+ :canceled_at, TIMESTAMP, data_key: ["cancellation", "canceled_at"], optional: true, index: true,
44
+ ),
45
+ Webhookdb::Replicator::Column.new(
46
+ :deposited_at, TIMESTAMP, data_key: ["deposit", "deposited_at"], optional: true, index: true,
47
+ ),
48
+ Webhookdb::Replicator::Column.new(
49
+ :mailed_at, TIMESTAMP, data_key: ["mailing", "mailed_at"], optional: true, index: true,
50
+ ),
51
+ Webhookdb::Replicator::Column.new(
52
+ :submitted_at, TIMESTAMP, data_key: ["submission", "submitted_at"], optional: true, index: true,
49
53
  ),
50
54
  ]
51
55
  end
52
56
 
53
- def _update_where_expr
54
- return self.qualified_table_sequel_identifier[:updated_at] < Sequel[:excluded][:updated_at]
55
- end
56
-
57
- def _resource_and_event(request)
58
- return self._find_resource_and_event(request.body, "check_transfer")
59
- end
60
-
61
- def _mixin_backfill_url
62
- return "#{self.service_integration.api_url}/check_transfers"
63
- end
57
+ def _mixin_object_type = "check_transfer"
64
58
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Webhookdb::Replicator::IncreaseEventV1 < Webhookdb::Replicator::Base
4
+ include Appydays::Loggable
5
+ include Webhookdb::Replicator::IncreaseV1Mixin
6
+
7
+ # @return [Webhookdb::Replicator::Descriptor]
8
+ def self.descriptor
9
+ return Webhookdb::Replicator::Descriptor.new(
10
+ name: "increase_event_v1",
11
+ ctor: self,
12
+ feature_roles: [],
13
+ resource_name_singular: "Increase Event",
14
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
15
+ # Since events are only done through the increase_app_v1,
16
+ # we don't support normal WebhookDB webhooks. Instead,
17
+ # they come in via the app. If we wanted to handle webhooks to the normal
18
+ # /v1/service_integrations/:opaque_id endpoint, rather than /v1/install/increase/webhook,
19
+ # we'd make this 'true' and have to do work like webhook validation.
20
+ # supports_webhooks: false,
21
+ supports_backfill: true,
22
+ api_docs_url: "https://increase.com/documentation/api",
23
+ )
24
+ end
25
+
26
+ def _remote_key_column
27
+ return Webhookdb::Replicator::Column.new(:increase_id, TEXT, data_key: "id")
28
+ end
29
+
30
+ def _denormalized_columns
31
+ return [
32
+ Webhookdb::Replicator::Column.new(:associated_object_id, TEXT, index: true),
33
+ Webhookdb::Replicator::Column.new(:associated_object_type, TEXT),
34
+ Webhookdb::Replicator::Column.new(:category, TEXT, index: true),
35
+ Webhookdb::Replicator::Column.new(:created_at, TIMESTAMP, index: true),
36
+ ]
37
+ end
38
+
39
+ def _timestamp_column_name = :created_at
40
+ def _mixin_object_type = "event"
41
+ end
@@ -11,10 +11,13 @@ class Webhookdb::Replicator::IncreaseLimitV1 < Webhookdb::Replicator::Base
11
11
  def self.descriptor
12
12
  return Webhookdb::Replicator::Descriptor.new(
13
13
  name: "increase_limit_v1",
14
- ctor: ->(sint) { Webhookdb::Replicator::IncreaseLimitV1.new(sint) },
15
- feature_roles: [],
14
+ ctor: self,
15
+ # This is a legacy resource. Instead, users should set the 'allow/deny ACH debits' flag on account numbers,
16
+ # or use the Inbound ACH Transfer object, which can send a webhook to accept or reject it.
17
+ # This flag is here for WebhookDB users who still need access to Limit resources.
18
+ feature_roles: ["increase_limits"],
16
19
  resource_name_singular: "Increase Limit",
17
- supports_webhooks: true,
20
+ dependency_descriptor: Webhookdb::Replicator::IncreaseAppV1.descriptor,
18
21
  supports_backfill: true,
19
22
  api_docs_url: "https://increase.com/documentation/api",
20
23
  )
@@ -34,45 +37,17 @@ class Webhookdb::Replicator::IncreaseLimitV1 < Webhookdb::Replicator::Base
34
37
  :row_created_at,
35
38
  TIMESTAMP,
36
39
  data_key: "created_at",
37
- event_key: "created_at",
38
- defaulter: :now,
39
- optional: true,
40
- index: true,
41
- ),
42
- Webhookdb::Replicator::Column.new(
43
- :row_updated_at,
44
- TIMESTAMP,
45
- data_key: "created_at",
46
- event_key: "created_at",
47
40
  defaulter: :now,
48
41
  optional: true,
49
42
  index: true,
50
43
  ),
44
+ Webhookdb::Replicator::Column.new(:row_updated_at, TIMESTAMP, data_key: "updated_at", index: true),
51
45
  Webhookdb::Replicator::Column.new(:status, TEXT),
52
46
  Webhookdb::Replicator::Column.new(:value, INTEGER),
53
47
  ]
54
48
  end
55
49
 
56
- def _timestamp_column_name
57
- return :row_updated_at
58
- end
50
+ def _timestamp_column_name = :row_updated_at
59
51
 
60
- def _update_where_expr
61
- return self.qualified_table_sequel_identifier[:row_updated_at] < Sequel[:excluded][:row_updated_at]
62
- end
63
-
64
- def _resource_and_event(request)
65
- return self._find_resource_and_event(request.body, "limit")
66
- end
67
-
68
- def _upsert_update_expr(inserting, **_kwargs)
69
- update = super
70
- # Only set created_at if it's not set so the initial insert isn't modified.
71
- self._coalesce_excluded_on_update(update, [:row_created_at])
72
- return update
73
- end
74
-
75
- def _mixin_backfill_url
76
- return "#{self.service_integration.api_url}/limits"
77
- end
52
+ def _mixin_object_type = "limit"
78
53
  end