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 303

#backfill_keyString

Returns Key for backfilling.

Returns:

  • (String)

    Key for backfilling.



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

#backfill_secretString

Returns Password/secret for backfilling.

Returns:

  • (String)

    Password/secret for backfilling.



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

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

#depends_onWebhookdb::ServiceIntegration



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

#opaque_idString

Returns:

  • (String)


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

#organizationWebhookdb::Organization



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

#service_nameString

Returns Lookup name of the service.

Returns:

  • (String)

    Lookup name of the service



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

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

#table_nameString

Returns Name of the table.

Returns:

  • (String)

    Name of the table



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

#webhook_secretString

Returns Secret used to sign webhooks.

Returns:

  • (String)

    Secret used to sign webhooks.



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

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

Class Method Details

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



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

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



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

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

Instance Method Details

#authed_api_pathObject



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

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

#before_createObject

:Sequel Hooks:



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

def before_create
  self.ensure_opaque_id
end

#can_be_modified_by?(customer) ⇒ Boolean

Returns:

  • (Boolean)


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

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:



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

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



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

def destroy_self_and_all_dependents
  self.dependents.each(&:destroy_self_and_all_dependents)

  begin
    self.replicator.admin_dataset(timeout: :fast) { |ds| ds.db << "DROP TABLE #{self.table_name}" }
  rescue Sequel::DatabaseError => e
    raise unless e.wrapped_exception.is_a?(PG::UndefinedTable)
  end
  self.destroy
end

#ensure_opaque_idObject



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

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

#ensure_sequence(skip_check: false) ⇒ Object



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

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

#ensure_sequence_sql(skip_check: false) ⇒ Object



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

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



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

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



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

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

#new_opaque_idObject



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

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

#plan_supports_integration?Boolean

Returns:

  • (Boolean)


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

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



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

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

#rename_table(to:) ⇒ Object



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

def rename_table(to:)
  Webhookdb::Organization::DatabaseMigration.guard_ongoing!(self.organization)
  unless Webhookdb::DBAdapter::VALID_IDENTIFIER.match?(to)
    msg = "Sorry, this is not a valid table name. " + Webhookdb::DBAdapter::INVALID_IDENTIFIER_MESSAGE
    msg += " And we see you what you did there ;)" if to.include?(";") && to.downcase.include?("drop")
    raise TableRenameError, msg
  end
  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



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

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

#requires_sequence?Boolean

Returns:

  • (Boolean)


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

def requires_sequence?
  return self.replicator.requires_sequence?
end

#sequence_nameObject



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

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

#sequence_nextvalObject



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

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

#statsWebhookdb::ServiceIntegration::Stats



182
183
184
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
# File 'lib/webhookdb/service_integration.rb', line 182

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



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

def unauthed_webhook_endpoint
  return Webhookdb.api_url + self.unauthed_webhook_path
end

#unauthed_webhook_pathObject



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

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