Class: Webhookdb::ServiceIntegration

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Admin::Linked

#admin_link

Instance Attribute Details

#api_urlString

Returns Root Url of the api to backfill from.

Returns:

  • (String)

    Root Url of the api to backfill from



# File 'lib/webhookdb/service_integration.rb', line 315

#backfill_keyString

Returns Key for backfilling.

Returns:

  • (String)

    Key for backfilling.



# File 'lib/webhookdb/service_integration.rb', line 318

#backfill_secretString

Returns Password/secret for backfilling.

Returns:

  • (String)

    Password/secret for backfilling.



# File 'lib/webhookdb/service_integration.rb', line 321

#data_encryption_secretString

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.

Returns:

  • (String)

    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

#depends_onWebhookdb::ServiceIntegration



# File 'lib/webhookdb/service_integration.rb', line 334

#opaque_idString

Returns:

  • (String)


# File 'lib/webhookdb/service_integration.rb', line 312

#organizationWebhookdb::Organization



# File 'lib/webhookdb/service_integration.rb', line 303

#service_nameString

Returns Lookup name of the service.

Returns:

  • (String)

    Lookup name of the service



# File 'lib/webhookdb/service_integration.rb', line 309

#skip_webhook_verificationBoolean

Returns Set this to disable webhook verification on this integration. Useful when replaying logged webhooks.

Returns:

  • (Boolean)

    Set this to disable webhook verification on this integration. Useful when replaying logged webhooks.



# File 'lib/webhookdb/service_integration.rb', line 342

#table_nameString

Returns Name of the table.

Returns:

  • (String)

    Name of the table



# File 'lib/webhookdb/service_integration.rb', line 306

#webhook_secretString

Returns Secret used to sign webhooks.

Returns:

  • (String)

    Secret used to sign webhooks.



# File 'lib/webhookdb/service_integration.rb', line 324

#webhookdb_api_keyString

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).

Returns:

  • (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. 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_pathObject



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_createObject

: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

Returns:

  • (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_candidatesArray<Webhookdb::ServiceIntegration>

Return service integrations that can be used as the dependency for this integration.

Returns:



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_dependentsObject



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_idObject



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_tagsObject



98
99
100
101
102
103
104
105
# File 'lib/webhookdb/service_integration.rb', line 98

def log_tags
  return {
    service_integration_id: self.id,
    service_integration_name: self.service_name,
    service_integration_table: self.table_name,
    **self.organization.log_tags,
  }
end

#new_api_keyObject



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_idObject



283
# File 'lib/webhookdb/service_integration.rb', line 283

def new_opaque_id = Webhookdb::Id.new_opaque_id("svi")

#plan_supports_integration?Boolean

Returns:

  • (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_dependenciesArray<Webhookdb::ServiceIntegration>

Return all service integrations this one depends on, in closest-ancestor order (that is, parent before grandparent).

Returns:



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_dependentsArray<Webhookdb::ServiceIntegration>

Return all dependents (integrations that depend on this one), breadth-first (that is, all children before grandchildren).

Returns:



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

#replicatorWebhookdb::Replicator::Base



94
95
96
# File 'lib/webhookdb/service_integration.rb', line 94

def replicator
  return Webhookdb::Replicator.create(self)
end

#requires_sequence?Boolean

Returns:

  • (Boolean)


261
262
263
# File 'lib/webhookdb/service_integration.rb', line 261

def requires_sequence?
  return self.replicator.requires_sequence?
end

#sequence_nameObject



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_nextvalObject



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

#statsWebhookdb::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_endpointObject



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_pathObject



111
112
113
# File 'lib/webhookdb/service_integration.rb', line 111

def unauthed_webhook_path
  return "/v1/service_integrations/#{self.opaque_id}"
end