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
@@ -10,13 +10,15 @@ require "webhookdb/async/audit_logger"
10
10
  require "webhookdb/jobs/process_webhook"
11
11
 
12
12
  class Webhookdb::API::ServiceIntegrations < Webhookdb::API::V1
13
+ include Webhookdb::Service::Types
14
+
13
15
  # These URLs are not used by the CLI-
14
16
  # they are the url that customers should point their webhooks to.
15
17
  # We can't check org permissions on this endpoint
16
18
  # because external services (so no auth) will be posting webhooks here.
17
19
  # Depend on webhook verification to ensure the request is valid.
18
20
  resource :service_integrations do
19
- route [:post, :put, :delete, :patch], "/:opaque_id*" do
21
+ route [:get, :post, :put, :delete, :patch], "/:opaque_id*" do
20
22
  opaque_id = params[:opaque_id]
21
23
  handle_webhook_request(opaque_id) do
22
24
  Webhookdb::ServiceIntegration[opaque_id:] or merror!(400, "No integration with that id")
@@ -41,7 +43,7 @@ class Webhookdb::API::ServiceIntegrations < Webhookdb::API::V1
41
43
  resource :create do
42
44
  helpers do
43
45
  def create_integration(org, name)
44
- available_services_list = org.available_replicator_names.sort.join("\n\t")
46
+ available_services_list = org.available_replicators.map(&:name).sort.join("\n\t")
45
47
 
46
48
  service_name_invalid = Webhookdb::Replicator.registered(name).nil?
47
49
  if service_name_invalid
@@ -55,7 +57,7 @@ Run `webhookdb services list` to see available services, and try again with the
55
57
  end
56
58
 
57
59
  # If org does not have access to the given service
58
- unless org.available_replicator_names.include?(name)
60
+ unless org.available_replicators.map(&:name).include?(name)
59
61
  step = Webhookdb::Replicator::StateMachineStep.new
60
62
  step.needs_input = false
61
63
  step.output =
@@ -95,13 +97,13 @@ If the list does not look correct, you can contact support at #{Webhookdb.suppor
95
97
  :guard_confirm,
96
98
  "WARNING: #{org.name} already has an integration for service #{params[:service_name]}.\n" \
97
99
  "Press Enter to create another, or Ctrl+C to quit.\n" \
98
- "Modify the existing integration using `webhookdb integrations #{existing.opaque_id} setup`",
100
+ "Modify the existing integration using `webhookdb integrations setup #{existing.opaque_id}`",
99
101
  )
100
102
  end
101
103
  end
102
104
  desc "Create service integration on a given organization"
103
105
  params do
104
- optional :service_name, type: String,
106
+ optional :service_name, type: TrimmedString,
105
107
  prompt: "Enter the name of the service to create an integration for.\n" \
106
108
  "Run 'webhookdb services list' to see available services:"
107
109
  optional :guard_confirm
@@ -187,7 +189,9 @@ If the list does not look correct, you can contact support at #{Webhookdb.suppor
187
189
 
188
190
  desc "Returns information about the integration."
189
191
  params do
190
- optional :field, type: String, values: Webhookdb::ServiceIntegration::INTEGRATION_INFO_FIELDS.keys + [""]
192
+ optional :field,
193
+ type: TrimmedString,
194
+ values: TrimmedString.map(Webhookdb::ServiceIntegration::INTEGRATION_INFO_FIELDS.keys + [""])
191
195
  end
192
196
  post :info do
193
197
  ensure_plan_supports!
@@ -339,7 +343,7 @@ If the list does not look correct, you can contact support at #{Webhookdb.suppor
339
343
  end
340
344
 
341
345
  params do
342
- optional :confirm, type: String
346
+ optional :confirm, type: TrimmedString
343
347
  end
344
348
  post :delete do
345
349
  ensure_plan_supports!
@@ -384,9 +388,11 @@ The tables and all data for this integration and its dependents will also be rem
384
388
 
385
389
  params do
386
390
  optional :new_name,
387
- type: String,
391
+ type: TrimmedString,
388
392
  db_identifier: true,
389
- prompt: "Enter the new name of the table. " + Webhookdb::DBAdapter::INVALID_IDENTIFIER_MESSAGE
393
+ prompt: "Enter the new name of the table. " +
394
+ Webhookdb::DBAdapter::INVALID_IDENTIFIER_PROMPT +
395
+ "\nTable name:"
390
396
  end
391
397
  post :rename_table do
392
398
  org = lookup_org!
@@ -6,6 +6,8 @@ require "webhookdb/api"
6
6
  require "webhookdb/admin_api"
7
7
 
8
8
  class Webhookdb::API::Subscriptions < Webhookdb::API::V1
9
+ include Webhookdb::Service::Types
10
+
9
11
  resource :organizations do
10
12
  route_param :org_identifier, type: String do
11
13
  resource :subscriptions do
@@ -19,7 +21,7 @@ class Webhookdb::API::Subscriptions < Webhookdb::API::V1
19
21
 
20
22
  desc "Authenticates stripe user and returns stripe checkout session or billing portal url"
21
23
  params do
22
- optional :plan, type: String
24
+ optional :plan, type: TrimmedString
23
25
  optional :guard_confirm
24
26
  end
25
27
  post :open_portal do
@@ -5,6 +5,8 @@ require "webhookdb/jobs/sync_target_run_sync"
5
5
 
6
6
  # rubocop:disable Layout/LineLength
7
7
  class Webhookdb::API::SyncTargets < Webhookdb::API::V1
8
+ include Webhookdb::Service::Types
9
+
8
10
  class ConnectionUrlType < Grape::Validations::Validators::Base
9
11
  def validate!(params)
10
12
  url = params[:connection_url]
@@ -46,14 +48,14 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
46
48
  disable: ->(req) { req.path.end_with?("/update") },
47
49
  }
48
50
  is_db && optional(:schema,
49
- type: String,
51
+ type: TrimmedString,
50
52
  db_identifier: true,
51
53
  allow_blank: true,
52
54
  desc: "Schema (or namespace) to write the table into. Default to no schema/namespace.",)
53
55
  # The description here says there is a default value, but the default value isn't actually saved to the SyncTarget
54
56
  # object--it's inferred in the SyncTarget sync behavior when the table value is a blank string.
55
57
  is_db && optional(:table,
56
- type: String,
58
+ type: TrimmedString,
57
59
  db_identifier: true,
58
60
  allow_blank: true,
59
61
  desc: "Table to create and update. Default to match the table name of the service integration.",)
@@ -64,7 +66,7 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
64
66
  end
65
67
  params :connection_url do
66
68
  optional :connection_url,
67
- type: String,
69
+ type: TrimmedString,
68
70
  prompt: "Enter the #{is_db ? 'database connection string' : 'HTTP endpoint'} that WebhookDB should sync data to:",
69
71
  connection_url_type: is_db ? "db" : "http"
70
72
  end
@@ -112,7 +114,7 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
112
114
  params do
113
115
  use :connection_url
114
116
  use :sync_target_params
115
- requires :service_integration_identifier, type: String, allow_blank: false
117
+ requires :service_integration_identifier, type: TrimmedString, allow_blank: false
116
118
  end
117
119
  route_setting :target_type, target_type_resource
118
120
  post :create do
@@ -153,8 +155,8 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
153
155
  end
154
156
  end
155
157
  params do
156
- optional :user, type: String, prompt: "Username for the connection:"
157
- optional :password, type: String, prompt: "Password for the connection:"
158
+ optional :user, type: TrimmedString, prompt: "Username for the connection:"
159
+ optional :password, type: TrimmedString, prompt: "Password for the connection:"
158
160
  end
159
161
  route_setting :target_type, target_type_resource
160
162
  post :update_credentials do
@@ -187,7 +189,7 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
187
189
  end
188
190
 
189
191
  params do
190
- optional :confirm, type: String
192
+ optional :confirm, type: TrimmedString
191
193
  end
192
194
  route_setting :target_type, target_type_resource
193
195
  post :delete do
@@ -32,6 +32,7 @@ class Webhookdb::API::System < Webhookdb::Service
32
32
  version: Webhookdb::VERSION,
33
33
  commit: Webhookdb::COMMIT,
34
34
  release: Webhookdb::RELEASE,
35
+ released_at: Webhookdb::RELEASE_CREATED_AT,
35
36
  log_level: Webhookdb.logger.level,
36
37
  }
37
38
  end
@@ -3,6 +3,8 @@
3
3
  require "webhookdb/api"
4
4
 
5
5
  class Webhookdb::API::WebhookSubscriptions < Webhookdb::API::V1
6
+ include Webhookdb::Service::Types
7
+
6
8
  resource :organizations do
7
9
  route_param :org_identifier, type: String do
8
10
  resource :webhook_subscriptions do
@@ -21,7 +23,7 @@ class Webhookdb::API::WebhookSubscriptions < Webhookdb::API::V1
21
23
 
22
24
  params do
23
25
  optional :service_integration_identifier,
24
- type: String,
26
+ type: TrimmedString,
25
27
  desc: "If provided, attach the webhook subscription to this integration rather than the org.",
26
28
  prompt: "Which integration is this for? Use the service name, table name, or opaque id.\n" \
27
29
  "See your integrations with `webhookdb integrations list`:"
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "grape"
4
4
  require "grape-swagger"
5
+ require "rack/dynamic_config_writer"
6
+ require "rack/spa_app"
5
7
  require "sidekiq/web"
6
8
  require "sidekiq/cron/web"
7
9
 
@@ -18,6 +20,7 @@ require "webhookdb/api/me"
18
20
  require "webhookdb/api/organizations"
19
21
  require "webhookdb/api/replay"
20
22
  require "webhookdb/api/saved_queries"
23
+ require "webhookdb/api/saved_views"
21
24
  require "webhookdb/api/service_integrations"
22
25
  require "webhookdb/api/services"
23
26
  require "webhookdb/api/stripe"
@@ -27,14 +30,16 @@ require "webhookdb/api/system"
27
30
  require "webhookdb/api/webhook_subscriptions"
28
31
 
29
32
  require "webhookdb/admin_api/auth"
30
- require "webhookdb/admin_api/customers"
31
33
  require "webhookdb/admin_api/database_documents"
32
- require "webhookdb/admin_api/message_deliveries"
33
- require "webhookdb/admin_api/roles"
34
+ require "webhookdb/admin_api/data_provider"
34
35
 
35
36
  require "webterm/apps"
36
37
 
37
38
  module Webhookdb::Apps
39
+ REDIRECTS = {
40
+ "/increase" => "/v1/install/increase",
41
+ }.freeze
42
+
38
43
  # Call this from your rackup file, like config.ru.
39
44
  #
40
45
  # @example
@@ -48,15 +53,22 @@ module Webhookdb::Apps
48
53
  def self.rack_up(config_ru)
49
54
  Webhookdb::Async.setup_web
50
55
  config_ru.instance_exec do
51
- map "/admin" do
56
+ map "/admin_api" do
52
57
  run Webhookdb::Apps::AdminAPI.build_app
53
58
  end
59
+ map "/admin" do
60
+ run Webhookdb::Apps::Admin.to_app
61
+ end
54
62
  map "/sidekiq" do
55
63
  run Webhookdb::Apps::SidekiqWeb.to_app
56
64
  end
57
65
  map "/terminal" do
58
66
  run Webhookdb::Apps::Webterm.to_app
59
67
  end
68
+ use Rack::SimpleRedirect, routes: (REDIRECTS.each_with_object({}) do |(k, v), memo|
69
+ memo[k] = v
70
+ memo["#{k}/"] = v
71
+ end)
60
72
  run Webhookdb::Apps::API.build_app
61
73
  end
62
74
  end
@@ -70,6 +82,7 @@ module Webhookdb::Apps
70
82
  mount Webhookdb::API::Organizations
71
83
  mount Webhookdb::API::Replay
72
84
  mount Webhookdb::API::SavedQueries
85
+ mount Webhookdb::API::SavedViews
73
86
  mount Webhookdb::API::ServiceIntegrations
74
87
  mount Webhookdb::API::Services
75
88
  mount Webhookdb::API::Stripe
@@ -83,12 +96,22 @@ module Webhookdb::Apps
83
96
  class AdminAPI < Webhookdb::Service
84
97
  mount Webhookdb::AdminAPI::Auth
85
98
  mount Webhookdb::AdminAPI::DatabaseDocuments
86
- mount Webhookdb::AdminAPI::MessageDeliveries
87
- mount Webhookdb::AdminAPI::Roles
88
- mount Webhookdb::AdminAPI::Customers
99
+ mount Webhookdb::AdminAPI::DataProvider
89
100
  add_swagger_documentation if ENV["RACK_ENV"] == "development"
90
101
  end
91
102
 
103
+ Admin = Rack::Builder.new do
104
+ build_dir = Pathname(__FILE__).dirname.parent.parent + "admin-dist"
105
+ dw = Rack::DynamicConfigWriter.new(build_dir + "index.html", global_assign: "window.whdbDynamicEnv")
106
+ env = {
107
+ "VITE_API_ROOT" => "/",
108
+ "VITE_RELEASE" => "admin@1.0.0",
109
+ "NODE_ENV" => "production",
110
+ }.merge(Rack::DynamicConfigWriter.pick_env("VITE_"))
111
+ index_bytes = dw.as_string(env)
112
+ Rack::SpaApp.run_spa_app(self, build_dir, enforce_ssl: Webhookdb::Service.enforce_ssl, index_bytes:)
113
+ end
114
+
92
115
  SidekiqWeb = Rack::Builder.new do
93
116
  use Sentry::Rack::CaptureExceptions if Webhookdb::Sentry.enabled?
94
117
  use Rack::Auth::Basic, "Protected Area" do |username, password|
@@ -11,6 +11,8 @@ class Webhookdb::Async::AuditLogger < Amigo::AuditLogger
11
11
  MAX_STR_LEN = 64
12
12
  STR_PREFIX_LEN = 12
13
13
 
14
+ def audit_log_level = Webhookdb::Async.audit_log_level
15
+
14
16
  def perform(event_json)
15
17
  j2 = event_json.dup
16
18
  j2["payload"] = self.trim_long_strings(j2["payload"], max_str_len: MAX_STR_LEN, str_prefix_len: STR_PREFIX_LEN)
@@ -27,6 +27,11 @@ module Webhookdb::Async
27
27
  # at `warn` level.
28
28
  setting :slow_job_seconds, 1.0
29
29
 
30
+ # The log level that Webhookdb::Async::AuditLogger logs at.
31
+ # By default, use :info, but :debug may be appropriate for higher-activity servers
32
+ # to reduce logging costs (the messages can be big).
33
+ setting :audit_log_level, :info
34
+
30
35
  setting :sidekiq_redis_url, "redis://localhost:6379/0", key: "REDIS_URL"
31
36
  setting :sidekiq_redis_provider, ""
32
37
  # For sidekiq web UI. Randomize a default so they will only be useful if set.
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Helper table so backfill jobs can take exclusive locks on a service integration.
4
+ # Otherwise we end up backfilling the same integration concurrently.
5
+ class Webhookdb::BackfillJob::ServiceIntegrationLock < Webhookdb::Postgres::Model(
6
+ :backfill_job_service_integration_locks,
7
+ )
8
+
9
+ many_to_one :service_integration, class: "Webhookdb::ServiceIntegration"
10
+ end
11
+
12
+ # Table: backfill_job_service_integration_locks
13
+ # -------------------------------------------------------------------------------------------------------------------------------------------------
14
+ # Columns:
15
+ # id | integer | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
16
+ # service_integration_id | integer | NOT NULL
17
+ # Indexes:
18
+ # backfill_job_service_integration_locks_pkey | PRIMARY KEY btree (id)
19
+ # backfill_job_service_integration_loc_service_integration_id_key | UNIQUE btree (service_integration_id)
20
+ # Foreign key constraints:
21
+ # backfill_job_service_integration_lo_service_integration_id_fkey | (service_integration_id) REFERENCES service_integrations(id) ON DELETE CASCADE
22
+ # -------------------------------------------------------------------------------------------------------------------------------------------------
@@ -15,6 +15,7 @@
15
15
  #
16
16
  class Webhookdb::BackfillJob < Webhookdb::Postgres::Model(:backfill_jobs)
17
17
  plugin :timestamps
18
+ plugin :text_searchable, terms: [:service_integration]
18
19
 
19
20
  many_to_one :service_integration, class: "Webhookdb::ServiceIntegration"
20
21
  many_to_one :parent_job, class: "Webhookdb::BackfillJob"
@@ -71,6 +72,12 @@ class Webhookdb::BackfillJob < Webhookdb::Postgres::Model(:backfill_jobs)
71
72
  self.child_jobs.each(&:enqueue)
72
73
  end
73
74
 
75
+ def ensure_service_integration_lock
76
+ return Webhookdb::BackfillJob::ServiceIntegrationLock.find_or_create_or_find(
77
+ service_integration_id: self.service_integration_id,
78
+ )
79
+ end
80
+
74
81
  #
75
82
  # :section: Sequel Hooks
76
83
  #
@@ -93,6 +100,8 @@ end
93
100
  # parent_job_id | integer |
94
101
  # created_by_id | integer |
95
102
  # incremental | boolean | NOT NULL
103
+ # criteria | jsonb | NOT NULL DEFAULT '{}'::jsonb
104
+ # text_search | tsvector |
96
105
  # Indexes:
97
106
  # backfill_jobs_pkey | PRIMARY KEY btree (id)
98
107
  # backfill_jobs_opaque_id_key | UNIQUE btree (opaque_id)
@@ -37,6 +37,7 @@ class Webhookdb::Customer < Webhookdb::Postgres::Model(:customers)
37
37
 
38
38
  plugin :timestamps
39
39
  plugin :soft_deletes
40
+ plugin :text_searchable, terms: [:name, :email]
40
41
 
41
42
  one_to_many :all_memberships, class: "Webhookdb::OrganizationMembership"
42
43
  one_to_many :invited_memberships,
@@ -329,6 +330,7 @@ end
329
330
  # name | text | NOT NULL DEFAULT ''::text
330
331
  # note | text | NOT NULL DEFAULT ''::text
331
332
  # opaque_id | text | NOT NULL
333
+ # text_search | tsvector |
332
334
  # Indexes:
333
335
  # customers_pkey | PRIMARY KEY btree (id)
334
336
  # customers_email_key | UNIQUE btree (email)
@@ -339,9 +341,12 @@ end
339
341
  # backfill_jobs | backfill_jobs_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
340
342
  # customer_reset_codes | customer_reset_codes_customer_id_fkey | (customer_id) REFERENCES customers(id) ON DELETE CASCADE
341
343
  # message_deliveries | message_deliveries_recipient_id_fkey | (recipient_id) REFERENCES customers(id) ON DELETE SET NULL
344
+ # oauth_sessions | oauth_sessions_customer_id_fkey | (customer_id) REFERENCES customers(id) ON DELETE CASCADE
342
345
  # organization_database_migrations | organization_database_migrations_started_by_id_fkey | (started_by_id) REFERENCES customers(id) ON DELETE SET NULL
343
346
  # organization_memberships | organization_memberships_customer_id_fkey | (customer_id) REFERENCES customers(id)
344
347
  # roles_customers | roles_customers_customer_id_fkey | (customer_id) REFERENCES customers(id)
348
+ # saved_queries | saved_queries_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
349
+ # saved_views | saved_views_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
345
350
  # sync_targets | sync_targets_created_by_id_fkey | (created_by_id) REFERENCES customers(id) ON DELETE SET NULL
346
351
  # webhook_subscriptions | webhook_subscriptions_created_by_id_fkey | (created_by_id) REFERENCES customers(id)
347
352
  # -----------------------------------------------------------------------------------------------------------------------------------------------------
@@ -52,7 +52,7 @@ class Webhookdb::DatabaseDocument < Webhookdb::Postgres::Model(:database_documen
52
52
  end
53
53
 
54
54
  def presigned_view_url(expire_at:, **kw)
55
- url = "#{Webhookdb.api_url}/admin/v1/database_documents/#{self.id}/view"
55
+ url = "#{Webhookdb.api_url}/admin_api/v1/database_documents/#{self.id}/view"
56
56
  return self.sign_url(url, expire_at:, **kw)
57
57
  end
58
58
  end
@@ -28,7 +28,7 @@ module Webhookdb::DBAdapter::DefaultSql
28
28
  def escape_identifier(s)
29
29
  s = s.to_s
30
30
  raise ArgumentError, "#{s} is an invalid identifier and should have been validated previously" unless
31
- Webhookdb::DBAdapter::VALID_IDENTIFIER.match?(s)
31
+ Webhookdb::DBAdapter.valid_identifier?(s)
32
32
 
33
33
  quo = self.identifier_quote_char
34
34
  return "#{quo}#{s}#{quo}" if RESERVED_KEYWORDS.include?(s.upcase) ||
@@ -6,10 +6,14 @@ class Webhookdb::DBAdapter
6
6
  class UnsupportedAdapter < StandardError; end
7
7
 
8
8
  VALID_IDENTIFIER = /^[a-zA-Z][a-zA-Z\d_ ]*$/
9
- INVALID_IDENTIFIER_MESSAGE = "Identifiers must start with a letter and " \
10
- "contain only letters, numbers, spaces, and underscores. " \
11
- "See https://docs.webhookdb.com/concepts/valid-identifiers/ for rules " \
12
- "about identifiers like schema, table, and column names."
9
+ INVALID_IDENTIFIER_PROMPT =
10
+ "Identifiers must start with a letter and contain only letters, numbers, spaces, and underscores.\n" \
11
+ "See https://docs.webhookdb.com/concepts/valid-identifiers/ for rules\n" \
12
+ "about identifiers like schema, table, and column names."
13
+
14
+ INVALID_IDENTIFIER_MESSAGE = INVALID_IDENTIFIER_PROMPT.tr("\n", " ")
15
+
16
+ class InvalidIdentifier < Webhookdb::InvalidInput; end
13
17
 
14
18
  class Schema < Webhookdb::TypedStruct
15
19
  attr_reader :name
@@ -202,6 +206,18 @@ class Webhookdb::DBAdapter
202
206
  def self.supported_adapters_message
203
207
  return "Postgres (postgres://), SnowflakeDB (snowflake://)"
204
208
  end
209
+
210
+ def self.valid_identifier?(s) = VALID_IDENTIFIER.match?(s)
211
+
212
+ # Raise if the identifier +s+ is invalid according to +VALID_IDENTIFIER+.
213
+ # +type+ is used in the error message, like 'Sorry, this is not a valid table name.'
214
+ # If the user tries SQL injection, let them know we noticed!
215
+ def self.validate_identifier!(s, type:)
216
+ return if self.valid_identifier?(s)
217
+ msg = "Sorry, this is not a valid #{type} name. #{INVALID_IDENTIFIER_MESSAGE}"
218
+ msg += " And we see you what you did there ;)" if s.include?(";") && s.downcase.include?("drop")
219
+ raise InvalidIdentifier, msg
220
+ end
205
221
  end
206
222
 
207
223
  require "webhookdb/db_adapter/pg"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/fixtures"
4
+
5
+ module Webhookdb::Fixtures::MessageBody
6
+ extend Webhookdb::Fixtures
7
+
8
+ fixtured_class Webhookdb::Message::Body
9
+
10
+ base :message_body do
11
+ self.mediatype ||= "text/plain"
12
+ self.content ||= Faker::Lorem.paragraph
13
+ end
14
+
15
+ before_saving do |instance|
16
+ instance.delivery ||= Webhookdb::Fixtures.message_delivery.create
17
+ instance
18
+ end
19
+
20
+ decorator :html do
21
+ self.mediatype = "text/html"
22
+ self.content = "<html><body><p>#{Faker::Lorem.sentence}</p></body></html>"
23
+ end
24
+
25
+ decorator :text do
26
+ self.mediatype = "text/plain"
27
+ self.content = Faker::Lorem.paragraph
28
+ end
29
+
30
+ decorator :subject do
31
+ self.mediatype = "subject"
32
+ self.content = Faker::Lorem.sentence
33
+ end
34
+ end
@@ -20,6 +20,11 @@ module Webhookdb::Fixtures::Organizations
20
20
  Webhookdb::Fixtures.organization_membership.verified.create(customer: c, organization: self)
21
21
  end
22
22
 
23
+ decorator :with_admin, presave: true do |c={}|
24
+ c = Webhookdb::Fixtures.customer.create(c) unless c.is_a?(Webhookdb::Customer)
25
+ Webhookdb::Fixtures.organization_membership.verified.admin.create(customer: c, organization: self)
26
+ end
27
+
23
28
  decorator :with_invite, presave: true do |c={}|
24
29
  c = Webhookdb::Fixtures.customer.create(c) unless c.is_a?(Webhookdb::Customer)
25
30
  Webhookdb::Fixtures.organization_membership.invite.create(customer: c, organization: self)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb"
4
+ require "webhookdb/fixtures"
5
+
6
+ module Webhookdb::Fixtures::Roles
7
+ extend Webhookdb::Fixtures
8
+
9
+ fixtured_class Webhookdb::Role
10
+
11
+ base :role do
12
+ self.name ||= Faker::Lorem.word + SecureRandom.hex(2)
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb"
4
+ require "webhookdb/fixtures"
5
+
6
+ module Webhookdb::Fixtures::SavedViews
7
+ extend Webhookdb::Fixtures
8
+
9
+ fixtured_class Webhookdb::SavedView
10
+
11
+ base :saved_view do
12
+ self.name ||= "testview_#{SecureRandom.hex(3)}"
13
+ self.sql ||= "SELECT 'fixtured' AS testcol"
14
+ end
15
+
16
+ before_saving do |instance|
17
+ instance.organization ||= Webhookdb::Fixtures.organization.create
18
+ instance
19
+ end
20
+
21
+ decorator :created_by do |c={}|
22
+ c = Webhookdb::Fixtures.customer.create(c) unless c.is_a?(Webhookdb::Customer)
23
+ self.created_by = c
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webhookdb/fixtures"
4
+
5
+ module Webhookdb::Fixtures::WebhookSubscriptionDeliveries
6
+ extend Webhookdb::Fixtures
7
+
8
+ fixtured_class Webhookdb::WebhookSubscription::Delivery
9
+
10
+ base :webhook_subscription_delivery do
11
+ self.payload ||= {}
12
+ end
13
+
14
+ before_saving do |instance|
15
+ instance.webhook_subscription ||= Webhookdb::Fixtures.webhook_subscription.create
16
+ instance
17
+ end
18
+ end
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "httparty"
4
-
3
+ require "appydays/configurable"
5
4
  require "appydays/loggable/httparty_formatter"
5
+ require "httparty"
6
6
 
7
7
  module Webhookdb::Http
8
+ include Appydays::Configurable
9
+ configurable(:http) do
10
+ setting :log_level, :debug
11
+ end
12
+
8
13
  # Error raised when some API has rate limited us.
9
14
  class BaseError < StandardError; end
10
15
 
@@ -88,6 +93,7 @@ module Webhookdb::Http
88
93
 
89
94
  raise ArgumentError, "must pass :logger keyword" unless options.key?(:logger)
90
95
  options[:log_format] = :appydays
96
+ options[:log_level] = self.log_level
91
97
  end
92
98
 
93
99
  # Convenience wrapper around Down that handles gzip.
@@ -19,5 +19,8 @@ module Webhookdb::Icalendar
19
19
  # or two threads for 7 hours. The resyncs are spread out across the sync period
20
20
  # (ie, no thundering herd every 8 hours), but it is still a good idea to sync as infrequently as possible.
21
21
  setting :sync_period_hours, 6
22
+
23
+ # Cancelled events that were cancelled this long ago are deleted from the database.
24
+ setting :stale_cancelled_event_threshold_days, 35
22
25
  end
23
26
  end