webhookdb 1.2.2 → 1.3.1

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 (131) 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/041_views.rb +20 -0
  17. data/db/migrations/042_sint_lock.rb +10 -0
  18. data/db/migrations/043_text_search.rb +28 -0
  19. data/db/migrations/044_oauth_session_token_cache.rb +21 -0
  20. data/integration/auth_spec.rb +2 -2
  21. data/lib/sequel/plugins/text_searchable.rb +165 -0
  22. data/lib/sequel/text_searchable.rb +42 -0
  23. data/lib/webhookdb/admin_api/auth.rb +24 -3
  24. data/lib/webhookdb/admin_api/data_provider.rb +196 -0
  25. data/lib/webhookdb/admin_api/entities.rb +143 -28
  26. data/lib/webhookdb/admin_api.rb +0 -2
  27. data/lib/webhookdb/api/auth.rb +5 -6
  28. data/lib/webhookdb/api/db.rb +31 -6
  29. data/lib/webhookdb/api/entities.rb +7 -1
  30. data/lib/webhookdb/api/helpers.rb +6 -25
  31. data/lib/webhookdb/api/install.rb +204 -79
  32. data/lib/webhookdb/api/organizations.rb +14 -12
  33. data/lib/webhookdb/api/saved_queries.rb +9 -3
  34. data/lib/webhookdb/api/saved_views.rb +99 -0
  35. data/lib/webhookdb/api/service_integrations.rb +15 -9
  36. data/lib/webhookdb/api/subscriptions.rb +3 -1
  37. data/lib/webhookdb/api/sync_targets.rb +9 -7
  38. data/lib/webhookdb/api/system.rb +1 -0
  39. data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
  40. data/lib/webhookdb/apps.rb +30 -7
  41. data/lib/webhookdb/async/audit_logger.rb +2 -0
  42. data/lib/webhookdb/async.rb +5 -0
  43. data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
  44. data/lib/webhookdb/backfill_job.rb +9 -0
  45. data/lib/webhookdb/customer.rb +5 -0
  46. data/lib/webhookdb/database_document.rb +1 -1
  47. data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
  48. data/lib/webhookdb/db_adapter.rb +20 -4
  49. data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
  50. data/lib/webhookdb/fixtures/organizations.rb +5 -0
  51. data/lib/webhookdb/fixtures/roles.rb +14 -0
  52. data/lib/webhookdb/fixtures/saved_views.rb +25 -0
  53. data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
  54. data/lib/webhookdb/http.rb +8 -2
  55. data/lib/webhookdb/icalendar.rb +3 -0
  56. data/lib/webhookdb/idempotency.rb +69 -22
  57. data/lib/webhookdb/increase.rb +69 -21
  58. data/lib/webhookdb/intercom.rb +10 -3
  59. data/lib/webhookdb/jobs/backfill.rb +3 -1
  60. data/lib/webhookdb/jobs/emailer.rb +0 -1
  61. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
  62. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
  63. data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
  64. data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
  65. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
  66. data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
  67. data/lib/webhookdb/message/body.rb +6 -4
  68. data/lib/webhookdb/message/delivery.rb +2 -0
  69. data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
  70. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
  71. data/lib/webhookdb/oauth/fake_provider.rb +44 -0
  72. data/lib/webhookdb/oauth/front_provider.rb +1 -2
  73. data/lib/webhookdb/oauth/increase_provider.rb +80 -0
  74. data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
  75. data/lib/webhookdb/oauth/session.rb +20 -0
  76. data/lib/webhookdb/oauth.rb +7 -21
  77. data/lib/webhookdb/organization/alerting.rb +2 -0
  78. data/lib/webhookdb/organization/database_migration.rb +3 -0
  79. data/lib/webhookdb/organization.rb +37 -6
  80. data/lib/webhookdb/organization_membership.rb +14 -7
  81. data/lib/webhookdb/postgres.rb +2 -0
  82. data/lib/webhookdb/replicator/base.rb +1 -0
  83. data/lib/webhookdb/replicator/docgen.rb +9 -1
  84. data/lib/webhookdb/replicator/fake.rb +2 -3
  85. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
  86. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
  87. data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
  88. data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
  89. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
  90. data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
  91. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
  92. data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
  93. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
  94. data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
  95. data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
  96. data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
  97. data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
  98. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
  99. data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
  100. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
  101. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
  102. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
  103. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
  104. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  105. data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
  106. data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
  107. data/lib/webhookdb/replicator/webhook_request.rb +4 -0
  108. data/lib/webhookdb/replicator.rb +8 -0
  109. data/lib/webhookdb/role.rb +5 -2
  110. data/lib/webhookdb/saved_query.rb +23 -0
  111. data/lib/webhookdb/saved_view.rb +73 -0
  112. data/lib/webhookdb/sentry.rb +2 -0
  113. data/lib/webhookdb/service/entities.rb +0 -4
  114. data/lib/webhookdb/service/helpers.rb +5 -0
  115. data/lib/webhookdb/service/middleware.rb +9 -0
  116. data/lib/webhookdb/service/types.rb +10 -8
  117. data/lib/webhookdb/service/validators.rb +1 -2
  118. data/lib/webhookdb/service/view_api.rb +1 -1
  119. data/lib/webhookdb/service_integration.rb +17 -15
  120. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
  121. data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
  122. data/lib/webhookdb/subscription.rb +2 -0
  123. data/lib/webhookdb/sync_target.rb +10 -2
  124. data/lib/webhookdb/tasks/message.rb +3 -1
  125. data/lib/webhookdb/version.rb +1 -1
  126. data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
  127. data/lib/webhookdb/webhook_subscription.rb +2 -0
  128. metadata +57 -9
  129. data/lib/webhookdb/admin_api/customers.rb +0 -63
  130. data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
  131. 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