webhookdb 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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