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, 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, Github, Heroku, Idempotency, Increase, InvalidInput, InvalidPostcondition, InvalidPrecondition, InvariantViolation, LockFailed, LoggedWebhook, Organization, OrganizationMembership, RegressionModeSkip, Replicator, Role, SavedQuery, Service, ServiceIntegration, Shopify, Slack, Snowflake, Stripe, Subscription, SyncTarget, TypedStruct, WebhookResponse, WebhookSubscription, Webterm, Xml

Constant Summary collapse

VERSION =
"1.2.2"
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

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



123
124
125
126
127
128
129
130
131
# File 'lib/webhookdb.rb', line 123

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.



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/webhookdb.rb', line 141

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



106
107
108
109
110
111
112
113
114
# File 'lib/webhookdb.rb', line 106

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)


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

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)


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

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.



209
210
211
# File 'lib/webhookdb.rb', line 209

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.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/webhookdb.rb', line 214

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)


161
162
163
164
165
166
167
# File 'lib/webhookdb.rb', line 161

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