Module: Webhookdb

Extended by:
MethodUtilities
Includes:
Appydays::Configurable, Appydays::Loggable
Defined in:
lib/webhookdb/json.rb,
lib/webhookdb/version.rb,
lib/webhookdb/envfixer.rb,
lib/webhookdb.rb

Defined Under Namespace

Modules: API, AWS, Admin, AdminAPI, Apps, Async, Console, Crypto, Dbutil, DemoMode, EmailOctopus, Enumerable, Envfixer, Errors, Fixtures, Formatting, Front, GoogleCalendar, Http, Icalendar, Id, IntegrationSpecHelpers, Intercom, Jobs, Json, Liquid, Message, Messages, MethodUtilities, MicrosoftCalendar, Nextpax, Oauth, PhoneNumber, Plaid, Platform, Plivo, Postgres, Postmark, Pry, Redis, Sentry, Signalwire, SpecHelpers, Sponsy, Tasks, Theranest, Transistor, Twilio, WindowsTZ Classes: AggregateResult, BackfillJob, Backfiller, Cloudflare, ConnectionCache, Convertkit, Customer, DBAdapter, DatabaseDocument, DatabaseLocked, DeveloperAlert, ExceptionCarrier, Github, Heroku, Idempotency, Increase, InvalidInput, InvalidPostcondition, InvalidPrecondition, InvariantViolation, LockFailed, LoggedWebhook, Organization, OrganizationMembership, ProgrammingError, RegressionModeSkip, Replicator, Role, SavedQuery, SavedView, Service, ServiceIntegration, Shopify, Slack, Snowflake, Stripe, Subscription, SyncTarget, SystemLogEvent, TypedStruct, WebhookResponse, WebhookSubscription, WebhookdbError, Webterm, Xml

Constant Summary collapse

VERSION =
"1.4.0"
APPLICATION_NAME =
"Webhookdb"
RACK_ENV =
Appydays::Configurable.fetch_env(["RACK_ENV", "RUBY_ENV"], "development")
COMMIT =
Appydays::Configurable.fetch_env(["COMMIT", "GIT_SHA", "HEROKU_SLUG_COMMIT"], "00000000")
RELEASE =
Appydays::Configurable.fetch_env(["RELEASE", "GIT_REF", "HEROKU_RELEASE_VERSION"], "unknown-release")
RELEASE_CREATED_AT =
Appydays::Configurable.fetch_env(
  ["RELEASE_CREATED_AT", "BUILT_AT", "HEROKU_RELEASE_CREATED_AT"],
  Time.at(0).utc.iso8601,
)
INTEGRATION_TESTS_ENABLED =
ENV.fetch("INTEGRATION_TESTS", false)
DATA_DIR =
Pathname(__FILE__).dirname.parent + "data"
NUMBERS_TO_WORDS =
{
  "0" => "zero",
  "1" => "one",
  "2" => "two",
  "3" => "three",
  "4" => "four",
  "5" => "five",
  "6" => "six",
  "7" => "seven",
  "8" => "eight",
  "9" => "nine",
}.freeze

Class Method Summary collapse

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Class Method Details

.admin_urlObject



102
# File 'lib/webhookdb.rb', line 102

def self.admin_url = self.api_url

.cached_get(key) ⇒ Object

If globals caching is enabled, see if there is a cached value under key and return it if so. If there is not, evaluate the given block and store that value. Generally used for looking up well-known database objects like certain roles.



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

def self.cached_get(key)
  if self.use_globals_cache
    result = self.globals_cache[key]
    return result if result
  end
  result = yield()
  (self.globals_cache[key] = result) if self.use_globals_cache
  return result
end

.idempotency_key(instance, *parts) ⇒ Object

Generate a key for the specified Sequel model instance and any additional parts that can be used for idempotent requests.



165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/webhookdb.rb', line 165

def self.idempotency_key(instance, *parts)
  key = "%s-%s" % [instance.class.implicit_table_name, instance.pk]

  if instance.respond_to?(:updated_at) && instance.updated_at
    parts << instance.updated_at
  elsif instance.respond_to?(:created_at) && instance.created_at
    parts << instance.created_at
  end
  parts << SecureRandom.hex(8) if self.bust_idempotency
  key << "-" << parts.map(&:to_s).join("-") unless parts.empty?

  return key
end

.load_appObject



114
115
116
117
118
119
120
121
122
# File 'lib/webhookdb.rb', line 114

def self.load_app
  $stdout.sync = true
  $stderr.sync = true

  Appydays::Loggable.configure_12factor(format: self.log_format, application: APPLICATION_NAME)

  require "webhookdb/postgres"
  Webhookdb::Postgres.load_models
end

.parse_bool(s) ⇒ Object

Raises:

  • (ArgumentError)


206
207
208
209
210
211
212
213
214
215
# File 'lib/webhookdb.rb', line 206

def self.parse_bool(s)
  # rubocop:disable Style/NumericPredicate
  return false if s == nil? || s.blank? || s == 0
  # rubocop:enable Style/NumericPredicate
  return true if s.is_a?(Integer)
  sb = s.to_s.downcase
  return true if ["true", "t", "yes", "y", "on", "1"].include?(sb)
  return false if ["false", "f", "no", "n", "off", "0"].include?(sb)
  raise ArgumentError, "unparseable bool: #{s.inspect}"
end

.regression_mode?Boolean

Regression mode is true when we re replaying webhooks locally, or for some other reason, want to disable certain checks we use in production. For example, we may want to ignore certain errors (like if integrations are missing dependency rows), or disable certain validations (like always assume the webhook is valid).

Returns:

  • (Boolean)


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

def self.regression_mode?
  return self.regression_mode
end

.request_user_and_adminObject

Return the request user and admin stored in TLS. See service.rb for implementation.

Note that the second return value (the admin) will be nil if not authed as an admin, and if an admin is impersonating, the impersonated customer is the first value.

Both values will be nil if no user is authed or this is called outside of a request.

Usually these fields should only be used where it would be sufficiently difficult to pass the current user through the stack. In the API, you should instead use the ‘current customer’ methods like current_customer, and admin_customer, NOT using TLS. Outside of the API, this should only be used for things like auditing; it should NOT, for example, ever be used to determine the ‘customer owner’ of objects being created. Nearly all code will be simpler if the current customer is passed around. But it would be too complex for some code (like auditing) so this system exists. Overuse of request_user_and_admin will inevitably lead to regret.



233
234
235
# File 'lib/webhookdb.rb', line 233

def self.request_user_and_admin
  return Thread.current[:request_user], Thread.current[:request_admin]
end

.set_request_user_and_admin(user, admin, &block) ⇒ Object

Return the request user stored in TLS. See service.rb for details.



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/webhookdb.rb', line 238

def self.set_request_user_and_admin(user, admin, &block)
  if !user.nil? && !admin.nil? && self.request_user_and_admin != [nil, nil]
    raise Webhookdb::InvalidPrecondition, "request user is already set: #{user}, #{admin}"
  end
  Thread.current[:request_user] = user
  Thread.current[:request_admin] = admin
  return if block.nil?
  begin
    yield
  ensure
    Thread.current[:request_user] = nil
    Thread.current[:request_admin] = nil
  end
end

.to_slug(s) ⇒ Object

Convert a string into something we consistently use for slugs: a-z, 0-9, and underscores only. Leading numbers are converted to words.

Acme + Corporation -> “acme_corporation” 1Byte -> “one_byte” 10Byte -> “one0_byte”

Raises:

  • (ArgumentError)


185
186
187
188
189
190
191
# File 'lib/webhookdb.rb', line 185

def self.to_slug(s)
  raise ArgumentError, "s cannot be nil" if s.nil?
  return "" if s.blank?
  slug = s.downcase.strip.gsub(/[^a-z0-9]/, "_").squeeze("_")
  slug = NUMBERS_TO_WORDS[slug.first] + slug[1..] if slug.first.match?(/[0-9]/)
  return slug
end