Class: Webhookdb::ServiceIntegration

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

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 302

#backfill_keyString

Returns Key for backfilling.

Returns:

  • (String)

    Key for backfilling.



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

#backfill_secretString

Returns Password/secret for backfilling.

Returns:

  • (String)

    Password/secret for backfilling.



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

#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 324

#depends_onWebhookdb::ServiceIntegration



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

#opaque_idString

Returns:

  • (String)


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

#organizationWebhookdb::Organization



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

#service_nameString

Returns Lookup name of the service.

Returns:

  • (String)

    Lookup name of the service



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

#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 329

#table_nameString

Returns Name of the table.

Returns:

  • (String)

    Name of the table



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

#webhook_secretString

Returns Secret used to sign webhooks.

Returns:

  • (String)

    Secret used to sign webhooks.



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

#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 314

Class Method Details

.create_disambiguated(service_name, **kwargs) ⇒ Webhookdb::ServiceIntegration



77
78
79
80
# File 'lib/webhookdb/service_integration.rb', line 77

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



83
84
85
# File 'lib/webhookdb/service_integration.rb', line 83

def self.for_api_key(key)
  return self.with_encrypted_value(:webhookdb_api_key, key).first
end

Instance Method Details

#authed_api_pathObject



105
106
107
# File 'lib/webhookdb/service_integration.rb', line 105

def authed_api_path
  return "/v1/organizations/#{self.organization_id}/service_integrations/#{self.opaque_id}"
end

#before_createObject

:Sequel Hooks:



286
287
288
# File 'lib/webhookdb/service_integration.rb', line 286

def before_create
  self.ensure_opaque_id
end

#can_be_modified_by?(customer) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/webhookdb/service_integration.rb', line 87

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:



135
136
137
138
139
140
# File 'lib/webhookdb/service_integration.rb', line 135

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



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/webhookdb/service_integration.rb', line 146

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



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

def ensure_opaque_id = self[:opaque_id] ||= self.new_opaque_id

#ensure_sequence(skip_check: false) ⇒ Object



256
257
258
# File 'lib/webhookdb/service_integration.rb', line 256

def ensure_sequence(skip_check: false)
  self.db << self.ensure_sequence_sql(skip_check:)
end

#ensure_sequence_sql(skip_check: false) ⇒ Object



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

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



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

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



274
275
276
277
278
279
280
# File 'lib/webhookdb/service_integration.rb', line 274

def new_api_key
  k = +"sk/"
  k << self.ensure_opaque_id
  k << "/"
  k << Webhookdb::Id.rand_enc(24)
  return k
end

#new_opaque_idObject



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

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

#plan_supports_integration?Boolean

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/webhookdb/service_integration.rb', line 117

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_dependentsObject



142
143
144
# File 'lib/webhookdb/service_integration.rb', line 142

def recursive_dependents
  return self.dependents + self.dependents.flat_map(&:recursive_dependents)
end

#rename_table(to:) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/webhookdb/service_integration.rb', line 226

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



92
93
94
# File 'lib/webhookdb/service_integration.rb', line 92

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

#requires_sequence?Boolean

Returns:

  • (Boolean)


248
249
250
# File 'lib/webhookdb/service_integration.rb', line 248

def requires_sequence?
  return self.replicator.requires_sequence?
end

#sequence_nameObject



252
253
254
# File 'lib/webhookdb/service_integration.rb', line 252

def sequence_name
  return "replicator_seq_org_#{self.organization_id}_#{self.service_name}_#{self.id}_seq"
end

#sequence_nextvalObject



266
267
268
# File 'lib/webhookdb/service_integration.rb', line 266

def sequence_nextval
  return self.db.select(Sequel.function(:nextval, self.sequence_name)).single_value
end

#statsWebhookdb::ServiceIntegration::Stats



185
186
187
188
189
190
191
192
193
194
195
196
197
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
# File 'lib/webhookdb/service_integration.rb', line 185

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



113
114
115
# File 'lib/webhookdb/service_integration.rb', line 113

def unauthed_webhook_endpoint
  return Webhookdb.api_url + self.unauthed_webhook_path
end

#unauthed_webhook_pathObject



109
110
111
# File 'lib/webhookdb/service_integration.rb', line 109

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