Class: Webhookdb::ServiceIntegration
- Inherits:
-
Object
- Object
- Webhookdb::ServiceIntegration
- Includes:
- Admin::Linked
- Defined in:
- lib/webhookdb/service_integration.rb
Defined Under Namespace
Classes: Stats, TableRenameError
Constant Summary collapse
- INTEGRATION_INFO_FIELDS =
We limit the information that a user can access through the CLI to these fields.
{ "id" => :opaque_id, "service" => :service_name, "table" => :table_name, "url" => :unauthed_webhook_endpoint, "webhook_secret" => :webhook_secret, "webhookdb_api_key" => :webhookdb_api_key, "api_url" => :api_url, }.freeze
Instance Attribute Summary collapse
-
#api_url ⇒ String
Root Url of the api to backfill from.
-
#backfill_key ⇒ String
Key for backfilling.
-
#backfill_secret ⇒ String
Password/secret for backfilling.
-
#data_encryption_secret ⇒ String
The encryption key used to encrypt data for this organization.
- #depends_on ⇒ Webhookdb::ServiceIntegration
- #opaque_id ⇒ String
- #organization ⇒ Webhookdb::Organization
-
#service_name ⇒ String
Lookup name of the service.
-
#skip_webhook_verification ⇒ Boolean
Set this to disable webhook verification on this integration.
-
#table_name ⇒ String
Name of the table.
-
#webhook_secret ⇒ String
Secret used to sign webhooks.
-
#webhookdb_api_key ⇒ String
API Key used in the Whdb-Api-Key header that can be used to identify this service integration (where the opaque id cannot be used), and is a secret so can be used for authentication.
Class Method Summary collapse
- .create_disambiguated(service_name, **kwargs) ⇒ Webhookdb::ServiceIntegration
- .for_api_key(key) ⇒ Webhookdb::ServiceIntegration
Instance Method Summary collapse
- #authed_api_path ⇒ Object
-
#before_create ⇒ Object
:Sequel Hooks:.
- #can_be_modified_by?(customer) ⇒ Boolean
-
#dependency_candidates ⇒ Array<Webhookdb::ServiceIntegration>
Return service integrations that can be used as the dependency for this integration.
- #destroy_self_and_all_dependents ⇒ Object
- #ensure_opaque_id ⇒ Object
- #ensure_sequence(skip_check: false) ⇒ Object
- #ensure_sequence_sql(skip_check: false) ⇒ Object
- #log_tags ⇒ Object
- #new_api_key ⇒ Object
- #new_opaque_id ⇒ Object
- #plan_supports_integration? ⇒ Boolean
-
#recursive_dependencies ⇒ Array<Webhookdb::ServiceIntegration>
Return all service integrations this one depends on, in closest-ancestor order (that is, parent before grandparent).
-
#recursive_dependents ⇒ Array<Webhookdb::ServiceIntegration>
Return all dependents (integrations that depend on this one), breadth-first (that is, all children before grandchildren).
- #rename_table(to:) ⇒ Object
- #replicator ⇒ Webhookdb::Replicator::Base
- #requires_sequence? ⇒ Boolean
- #sequence_name ⇒ Object
- #sequence_nextval ⇒ Object
- #stats ⇒ Webhookdb::ServiceIntegration::Stats
- #unauthed_webhook_endpoint ⇒ Object
- #unauthed_webhook_path ⇒ Object
Methods included from Admin::Linked
Instance Attribute Details
#api_url ⇒ String
Returns Root Url of the api to backfill from.
|
# File 'lib/webhookdb/service_integration.rb', line 315
|
#backfill_key ⇒ String
Returns Key for backfilling.
|
# File 'lib/webhookdb/service_integration.rb', line 318
|
#backfill_secret ⇒ String
Returns Password/secret for backfilling.
|
# File 'lib/webhookdb/service_integration.rb', line 321
|
#data_encryption_secret ⇒ String
Returns The encryption key used to encrypt data for this organization. Note that this field is itself encrypted using Sequel encryption; its decrypted value is meant to be used as the data encryption key.
|
# File 'lib/webhookdb/service_integration.rb', line 337
|
#opaque_id ⇒ String
|
# File 'lib/webhookdb/service_integration.rb', line 312
|
#service_name ⇒ String
Returns Lookup name of the service.
|
# File 'lib/webhookdb/service_integration.rb', line 309
|
#skip_webhook_verification ⇒ Boolean
Returns Set this to disable webhook verification on this integration. Useful when replaying logged webhooks.
|
# File 'lib/webhookdb/service_integration.rb', line 342
|
#table_name ⇒ String
Returns Name of the table.
|
# File 'lib/webhookdb/service_integration.rb', line 306
|
#webhook_secret ⇒ String
Returns Secret used to sign webhooks.
|
# File 'lib/webhookdb/service_integration.rb', line 324
|
#webhookdb_api_key ⇒ String
Returns API Key used in the Whdb-Api-Key header that can be used to identify this service integration (where the opaque id cannot be used), and is a secret so can be used for authentication. Need for this should be rare- it’s usually only used outside of the core webhookdb/backfill design like for two-way sync (Front Channel/Signalwire integration, for example).
|
# File 'lib/webhookdb/service_integration.rb', line 327
|
Class Method Details
.create_disambiguated(service_name, **kwargs) ⇒ Webhookdb::ServiceIntegration
79 80 81 82 |
# File 'lib/webhookdb/service_integration.rb', line 79 def self.create_disambiguated(service_name, **kwargs) kwargs[:table_name] ||= "#{service_name}_#{SecureRandom.hex(2)}" return self.create(service_name:, **kwargs) end |
.for_api_key(key) ⇒ Webhookdb::ServiceIntegration
85 86 87 |
# File 'lib/webhookdb/service_integration.rb', line 85 def self.for_api_key(key) return self.with_encrypted_value(:webhookdb_api_key, key).first end |
Instance Method Details
#authed_api_path ⇒ Object
107 108 109 |
# File 'lib/webhookdb/service_integration.rb', line 107 def authed_api_path return "/v1/organizations/#{self.organization_id}/service_integrations/#{self.opaque_id}" end |
#before_create ⇒ Object
:Sequel Hooks:
299 300 301 |
# File 'lib/webhookdb/service_integration.rb', line 299 def before_create self.ensure_opaque_id end |
#can_be_modified_by?(customer) ⇒ Boolean
89 90 91 |
# File 'lib/webhookdb/service_integration.rb', line 89 def can_be_modified_by?(customer) return customer.verified_member_of?(self.organization) end |
#dependency_candidates ⇒ Array<Webhookdb::ServiceIntegration>
Return service integrations that can be used as the dependency for this integration.
137 138 139 140 141 142 |
# File 'lib/webhookdb/service_integration.rb', line 137 def dependency_candidates dep_descr = self.replicator.descriptor.dependency_descriptor return [] if dep_descr.nil? return self.organization.service_integrations. select { |si| si.service_name == dep_descr.name } end |
#destroy_self_and_all_dependents ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/webhookdb/service_integration.rb', line 159 def destroy_self_and_all_dependents self.dependents.each(&:destroy_self_and_all_dependents) if self.organization.admin_connection_url.present? begin self.replicator.admin_dataset(timeout: :fast) { |ds| ds.db << "DROP TABLE #{self.table_name}" } rescue Sequel::DatabaseError => e raise e unless e.wrapped_exception.is_a?(PG::UndefinedTable) end end self.destroy end |
#ensure_opaque_id ⇒ Object
285 |
# File 'lib/webhookdb/service_integration.rb', line 285 def ensure_opaque_id = self[:opaque_id] ||= self.new_opaque_id |
#ensure_sequence(skip_check: false) ⇒ Object
269 270 271 |
# File 'lib/webhookdb/service_integration.rb', line 269 def ensure_sequence(skip_check: false) self.db << self.ensure_sequence_sql(skip_check:) end |
#ensure_sequence_sql(skip_check: false) ⇒ Object
273 274 275 276 277 |
# File 'lib/webhookdb/service_integration.rb', line 273 def ensure_sequence_sql(skip_check: false) raise Webhookdb::InvalidPrecondition, "#{self.service_name} does not require sequence" if !skip_check && !self.requires_sequence? return "CREATE SEQUENCE IF NOT EXISTS #{self.sequence_name}" end |
#log_tags ⇒ Object
98 99 100 101 102 103 104 105 |
# File 'lib/webhookdb/service_integration.rb', line 98 def return { service_integration_id: self.id, service_integration_name: self.service_name, service_integration_table: self.table_name, **self.organization., } end |
#new_api_key ⇒ Object
287 288 289 290 291 292 293 |
# File 'lib/webhookdb/service_integration.rb', line 287 def new_api_key k = +"sk/" k << self.ensure_opaque_id k << "/" k << Webhookdb::Id.rand_enc(24) return k end |
#new_opaque_id ⇒ Object
283 |
# File 'lib/webhookdb/service_integration.rb', line 283 def new_opaque_id = Webhookdb::Id.new_opaque_id("svi") |
#plan_supports_integration? ⇒ Boolean
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/webhookdb/service_integration.rb', line 119 def plan_supports_integration? # if the sint's organization has an active subscription, return true return true if self.organization.active_subscription? # if there is no active subscription, check whether the integration is one of the first two # created by the organization limit = Webhookdb::Subscription.max_free_integrations free_integrations = Webhookdb::ServiceIntegration. where(organization: self.organization).order(:created_at, :id).limit(limit).all free_integrations.each do |sint| return true if sint.id == self.id end # if not, the integration is not supported return false end |
#recursive_dependencies ⇒ Array<Webhookdb::ServiceIntegration>
Return all service integrations this one depends on, in closest-ancestor order (that is, parent before grandparent).
154 155 156 157 |
# File 'lib/webhookdb/service_integration.rb', line 154 def recursive_dependencies return [] if self.depends_on.nil? return [self.depends_on].concat(self.depends_on.recursive_dependencies) end |
#recursive_dependents ⇒ Array<Webhookdb::ServiceIntegration>
Return all dependents (integrations that depend on this one), breadth-first (that is, all children before grandchildren).
147 148 149 |
# File 'lib/webhookdb/service_integration.rb', line 147 def recursive_dependents return self.dependents + self.dependents.flat_map(&:recursive_dependents) end |
#rename_table(to:) ⇒ Object
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/webhookdb/service_integration.rb', line 239 def rename_table(to:) Webhookdb::Organization::DatabaseMigration.guard_ongoing!(self.organization) Webhookdb::DBAdapter.validate_identifier!(to, type: "table") self.db.transaction do begin self.organization.admin_connection { |db| db << "ALTER TABLE #{self.table_name} RENAME TO #{to}" } rescue Sequel::DatabaseError => e case e.wrapped_exception when PG::DuplicateTable raise TableRenameError, "There is already a table named \"#{to}\". Run `webhookdb db tables` to see available tables." when PG::SyntaxError raise TableRenameError, "Please try again with double quotes around '#{to}' since it contains invalid identifier characters." else raise e end end self.update(table_name: to) end end |
#replicator ⇒ Webhookdb::Replicator::Base
94 95 96 |
# File 'lib/webhookdb/service_integration.rb', line 94 def replicator return Webhookdb::Replicator.create(self) end |
#requires_sequence? ⇒ Boolean
261 262 263 |
# File 'lib/webhookdb/service_integration.rb', line 261 def requires_sequence? return self.replicator.requires_sequence? end |
#sequence_name ⇒ Object
265 266 267 |
# File 'lib/webhookdb/service_integration.rb', line 265 def sequence_name return "replicator_seq_org_#{self.organization_id}_#{self.service_name}_#{self.id}_seq" end |
#sequence_nextval ⇒ Object
279 280 281 |
# File 'lib/webhookdb/service_integration.rb', line 279 def sequence_nextval return self.db.select(Sequel.function(:nextval, self.sequence_name)).single_value end |
#stats ⇒ Webhookdb::ServiceIntegration::Stats
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/webhookdb/service_integration.rb', line 198 def stats all_logged_webhooks = Webhookdb::LoggedWebhook.where( service_integration_opaque_id: self.opaque_id, ).where { inserted_at > 7.days.ago } if all_logged_webhooks.empty? return Stats.new( "We have no record of receiving webhooks for that integration in the past seven days.", {}, ) end # rubocop:disable Naming/VariableNumber count_last_7_days = all_logged_webhooks.count rejected_last_7_days = all_logged_webhooks.where { response_status >= 400 }.count success_last_7_days = (count_last_7_days - rejected_last_7_days) rejected_last_7_days_percent = (rejected_last_7_days.to_f / count_last_7_days) success_last_7_days_percent = (success_last_7_days.to_f / count_last_7_days) last_10 = Webhookdb::LoggedWebhook.order_by(Sequel.desc(:inserted_at)).limit(10).select_map(:response_status) last_10_success, last_10_rejected = last_10.partition { |rs| rs < 400 } data = { count_last_7_days:, count_last_7_days_formatted: count_last_7_days.to_s, success_last_7_days:, success_last_7_days_formatted: success_last_7_days.to_s, success_last_7_days_percent:, success_last_7_days_percent_formatted: "%.1f%%" % (success_last_7_days_percent * 100), rejected_last_7_days:, rejected_last_7_days_formatted: rejected_last_7_days.to_s, rejected_last_7_days_percent:, rejected_last_7_days_percent_formatted: "%.1f%%" % (rejected_last_7_days_percent * 100), successful_of_last_10: last_10_success.size, successful_of_last_10_formatted: last_10_success.size.to_s, rejected_of_last_10: last_10_rejected.size, rejected_of_last_10_formatted: last_10_rejected.size.to_s, } # rubocop:enable Naming/VariableNumber return Stats.new("", data) end |
#unauthed_webhook_endpoint ⇒ Object
115 116 117 |
# File 'lib/webhookdb/service_integration.rb', line 115 def unauthed_webhook_endpoint return Webhookdb.api_url + self.unauthed_webhook_path end |
#unauthed_webhook_path ⇒ Object
111 112 113 |
# File 'lib/webhookdb/service_integration.rb', line 111 def unauthed_webhook_path return "/v1/service_integrations/#{self.opaque_id}" end |