webhookdb 0.1.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/integration/async_spec.rb +15 -0
- data/integration/auth_spec.rb +53 -0
- data/integration/database_spec.rb +82 -0
- data/integration/helpers_spec.rb +21 -0
- data/integration/service_integrations_spec.rb +72 -0
- data/integration/system_spec.rb +15 -0
- data/lib/webhookdb/api/system.rb +9 -4
- data/lib/webhookdb/apps.rb +26 -0
- data/lib/webhookdb/async/autoscaler.rb +31 -6
- data/lib/webhookdb/heroku.rb +1 -1
- data/lib/webhookdb/pry.rb +72 -0
- data/lib/webhookdb/replicator/docgen.rb +4 -3
- data/lib/webhookdb/replicator.rb +5 -2
- data/lib/webhookdb/spec_helpers/integration.rb +2 -2
- data/lib/webhookdb/tasks/release.rb +6 -0
- data/lib/webhookdb/tasks/specs.rb +1 -1
- data/lib/webhookdb/tasks.rb +30 -0
- data/lib/webhookdb/version.rb +1 -1
- data/lib/webhookdb.rb +19 -4
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd104a25c13b1c3d4a9500bdb618c2f704692c26a09a307eb926cf87bcb2befe
|
4
|
+
data.tar.gz: e61ed8994471efa0ab7ab448cf12b68cf4cbb44152df3e98024da3ffa1985c5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a0839a974acd4cc643501fe544461e0848f3ee01e1e2b47bcb325dba10636556d15bb597532adb7039caa2dd4b8cf9be6fe487a4cfa211bdd82505dda1c3cc2
|
7
|
+
data.tar.gz: 4b619d15175fefaea0eafe39a57aa127c2a1b41beadf5f87fb7586e233170f30c51c02b4e20aa4701e695a42031459d9b1f11fb2ddae4ff1aa8f3d94a98aa4ee
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec/eventually"
|
4
|
+
|
5
|
+
require "webhookdb/async"
|
6
|
+
|
7
|
+
RSpec.describe "async workers", :integration do
|
8
|
+
it "emails the customer on reset code create" do
|
9
|
+
cu = with_async_publisher do
|
10
|
+
Webhookdb::Fixtures.reset_code.email.create
|
11
|
+
end
|
12
|
+
|
13
|
+
expect { cu.customer.refresh.message_deliveries }.to eventually(have_attributes(length: 1))
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "auth", :integration do
|
4
|
+
let(:password) { Webhookdb::Fixtures::Customers::PASSWORD }
|
5
|
+
|
6
|
+
it "allows me to sign up with a token, and log out" do
|
7
|
+
customer = Webhookdb::Fixtures.customer.create
|
8
|
+
code = Webhookdb::Fixtures.reset_code(customer:).create
|
9
|
+
|
10
|
+
login_resp = post(
|
11
|
+
"/v1/auth",
|
12
|
+
body: {email: customer.email, token: code.token},
|
13
|
+
)
|
14
|
+
expect(login_resp).to party_status(200)
|
15
|
+
|
16
|
+
customer_resp = get("/v1/me")
|
17
|
+
expect(customer_resp).to party_status(200)
|
18
|
+
|
19
|
+
logout_resp = post("/v1/auth/logout")
|
20
|
+
expect(logout_resp).to party_status(200)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows me to login via OTP, and logout" do
|
24
|
+
customer = Webhookdb::Fixtures.customer.instance
|
25
|
+
|
26
|
+
login_resp = post("/v1/auth", body: {email: customer.email})
|
27
|
+
expect(login_resp).to party_status(202)
|
28
|
+
|
29
|
+
customer = Webhookdb::Customer[email: customer.email]
|
30
|
+
login_resp = post("/v1/auth/login_otp/#{customer.opaque_id}", body: {value: customer.reset_codes.last.token})
|
31
|
+
expect(login_resp).to party_status(200)
|
32
|
+
|
33
|
+
customer_resp = get("/v1/me")
|
34
|
+
expect(customer_resp).to party_status(200)
|
35
|
+
|
36
|
+
logout_resp = post("/v1/auth/logout")
|
37
|
+
expect(logout_resp).to party_status(200)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can access admin endpoints only if the customer authed as an admin and retains the role" do
|
41
|
+
customer = Webhookdb::Fixtures.customer.admin.instance
|
42
|
+
auth_customer(customer)
|
43
|
+
|
44
|
+
resp = get("/admin/v1/auth")
|
45
|
+
expect(resp).to party_status(200)
|
46
|
+
expect(resp).to party_response(match(hash_including(name: customer.name)))
|
47
|
+
|
48
|
+
customer.remove_role(Webhookdb::Role.admin_role)
|
49
|
+
|
50
|
+
resp = get("/admin/v1/auth")
|
51
|
+
expect(resp).to party_status(401)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "database", :integration do
|
4
|
+
def setup_integration_with_data(rows)
|
5
|
+
org = Webhookdb::Fixtures.organization.create
|
6
|
+
org.prepare_database_connections
|
7
|
+
sint = Webhookdb::Fixtures.service_integration(organization: org).create
|
8
|
+
sint.replicator.create_table
|
9
|
+
Array.new(rows) do |i|
|
10
|
+
t = (Time.now - i.days).iso8601
|
11
|
+
sint.replicator.upsert_webhook_body({"my_id" => i.to_s, "at" => t})
|
12
|
+
end
|
13
|
+
Webhookdb::Organization::DbBuilder.new(org).prepare_database_connections
|
14
|
+
return sint
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can sync to database sync targets" do
|
18
|
+
sint = setup_integration_with_data(5)
|
19
|
+
sync_tgt = Webhookdb::Fixtures.sync_target(
|
20
|
+
service_integration: sint,
|
21
|
+
connection_url: sint.organization.admin_connection_url,
|
22
|
+
).create
|
23
|
+
@to_destroy << sync_tgt
|
24
|
+
require "webhookdb/jobs/sync_target_run_sync"
|
25
|
+
Webhookdb::Jobs::SyncTargetRunSync.perform_async(sync_tgt.id)
|
26
|
+
expect { sync_tgt.refresh }.to eventually(have_attributes(last_synced_at: be_present))
|
27
|
+
Sequel.connect(sint.organization.readonly_connection_url) do |db|
|
28
|
+
expect(db[sint.table_name.to_sym].all).to have_attributes(size: 5)
|
29
|
+
end
|
30
|
+
expect(sync_tgt.advisory_lock(sync_tgt.db).dataset(this: true).all).to be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can sync to http sync targets" do
|
34
|
+
sint = setup_integration_with_data(5)
|
35
|
+
sync_tgt = Webhookdb::Fixtures.sync_target(
|
36
|
+
service_integration: sint,
|
37
|
+
connection_url: "http://u:p@localhost:18015/mypath",
|
38
|
+
).create
|
39
|
+
@to_destroy << sync_tgt
|
40
|
+
|
41
|
+
require "socket"
|
42
|
+
server = TCPServer.new "localhost", 18_015
|
43
|
+
received = []
|
44
|
+
server_thread = Thread.new do
|
45
|
+
# Will only have one session
|
46
|
+
session = server.accept
|
47
|
+
# Processing line by line isn't needed here, we're just testing so grab the whole body
|
48
|
+
received << session.recv(4096)
|
49
|
+
session.print "HTTP/1.1 200\r\n"
|
50
|
+
session.print "Content-Type: text/plain\r\n"
|
51
|
+
session.print "\r\n"
|
52
|
+
session.print "ok"
|
53
|
+
session.close
|
54
|
+
end
|
55
|
+
|
56
|
+
# We need to do this in-process so the sync can POST back to localhost;
|
57
|
+
# if the worker was on another machine, the tcp server wouldn't be reachable.
|
58
|
+
sync_tgt.run_sync(now: Time.now)
|
59
|
+
expect { sync_tgt.refresh }.to eventually(have_attributes(last_synced_at: be_present))
|
60
|
+
expect { received }.to eventually(contain_exactly(include("POST /mypath").and(include('"rows":'))))
|
61
|
+
Thread.kill(server_thread)
|
62
|
+
expect(sync_tgt.advisory_lock(sync_tgt.db).dataset(this: true).all).to be_empty
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can run a database migration" do
|
66
|
+
sint = setup_integration_with_data(5)
|
67
|
+
org = sint.organization
|
68
|
+
dbmigration = with_async_publisher do
|
69
|
+
Webhookdb::Organization::DatabaseMigration.enqueue(
|
70
|
+
admin_connection_url_raw: org.admin_connection_url,
|
71
|
+
readonly_connection_url_raw: org.readonly_connection_url,
|
72
|
+
public_host: org.public_host,
|
73
|
+
started_by: nil,
|
74
|
+
organization: org,
|
75
|
+
)
|
76
|
+
end
|
77
|
+
expect { dbmigration.refresh }.to eventually(have_attributes(status: "finished"))
|
78
|
+
Sequel.connect(org.readonly_connection_url) do |db|
|
79
|
+
expect(db[Sequel[org.replication_schema.to_sym][sint.table_name.to_sym]].all).to have_attributes(size: 5)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "helpers", :integration do
|
4
|
+
it "can use the redis cache" do
|
5
|
+
key = "integration-test-key"
|
6
|
+
Webhookdb::Redis.cache.with do |r|
|
7
|
+
r.call("SET", key, "1")
|
8
|
+
expect(r.call("GET", key)).to eq("1")
|
9
|
+
end
|
10
|
+
t = Thread.start do
|
11
|
+
Webhookdb::Redis.cache.with do |r|
|
12
|
+
r.call("DEL", key)
|
13
|
+
expect(r.call("GET", key)).to be_nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
t.join
|
17
|
+
Webhookdb::Redis.cache.with do |r|
|
18
|
+
expect(r.call("GET", key)).to be_nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec/eventually"
|
4
|
+
|
5
|
+
RSpec.describe "service integrations", :integration do
|
6
|
+
def catch_missing_db(default)
|
7
|
+
yield
|
8
|
+
rescue Sequel::DatabaseError
|
9
|
+
return default
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can create a full webhookdb customer integration and POST webhooks" do
|
13
|
+
c = auth_customer
|
14
|
+
expect { c.refresh.all_memberships }.to eventually(have_attributes(length: 1))
|
15
|
+
org = c.verified_memberships.last.organization
|
16
|
+
expect { org.refresh }.to eventually(have_attributes(readonly_connection_url: be_present))
|
17
|
+
org.add_feature_role(Webhookdb::Role.find_or_create(name: "internal"))
|
18
|
+
|
19
|
+
resp = post(
|
20
|
+
"/v1/organizations/#{org.id}/service_integrations/create",
|
21
|
+
body: {service_name: "webhookdb_customer_v1"},
|
22
|
+
)
|
23
|
+
expect(resp).to party_status(200)
|
24
|
+
|
25
|
+
expect(org.refresh.service_integrations).to have_attributes(length: 1)
|
26
|
+
sint = org.service_integrations.first
|
27
|
+
|
28
|
+
expect do
|
29
|
+
catch_missing_db(["default"]) { sint.replicator.readonly_dataset(&:all) }
|
30
|
+
end.to eventually(be_empty)
|
31
|
+
|
32
|
+
with_async_publisher do
|
33
|
+
Webhookdb::Fixtures.customer.create
|
34
|
+
end
|
35
|
+
|
36
|
+
expect do
|
37
|
+
catch_missing_db(["default"]) { sint.replicator.readonly_dataset(&:all) }
|
38
|
+
end.to eventually(have_attributes(length: 1))
|
39
|
+
|
40
|
+
# puts sint.opaque_id, "/v1/service_integrations/#{sint.opaque_id}"
|
41
|
+
resp = post(
|
42
|
+
"/v1/service_integrations/#{sint.opaque_id}",
|
43
|
+
body: c.values.as_json,
|
44
|
+
headers: {"Whdb-Secret" => sint.webhook_secret},
|
45
|
+
json: true,
|
46
|
+
)
|
47
|
+
expect(resp).to party_status(202)
|
48
|
+
expect(resp).to party_response(match(o: "k"))
|
49
|
+
logged_whs = Webhookdb::LoggedWebhook.where(service_integration_opaque_id: sint.opaque_id).all
|
50
|
+
expect(logged_whs).to_not be_empty
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can upsert data synchrononously through endpoint" do
|
54
|
+
c = auth_customer
|
55
|
+
expect { c.refresh.all_memberships }.to eventually(have_attributes(length: 1))
|
56
|
+
org = c.verified_memberships.last.organization
|
57
|
+
expect { org.refresh }.to eventually(have_attributes(readonly_connection_url: be_present))
|
58
|
+
org.add_feature_role(Webhookdb::Role.find_or_create(name: "internal"))
|
59
|
+
sint = Webhookdb::Fixtures.service_integration(organization: org).create
|
60
|
+
sint.replicator.create_table
|
61
|
+
|
62
|
+
resp = post(
|
63
|
+
"/v1/organizations/#{org.id}/service_integrations/#{sint.opaque_id}/upsert",
|
64
|
+
body: {my_id: "id", at: Time.now},
|
65
|
+
json: true,
|
66
|
+
)
|
67
|
+
expect(resp).to party_status(200)
|
68
|
+
expect(resp).to party_response(match(hash_including(message: /You have upserted/)))
|
69
|
+
|
70
|
+
expect(sint.replicator.readonly_dataset(&:all)).to contain_exactly(include(my_id: "id"))
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "system", :integration do
|
4
|
+
it "responds to a health check" do
|
5
|
+
response = HTTParty.get(url("/healthz"))
|
6
|
+
expect(response).to party_status(200)
|
7
|
+
expect(response).to party_response(eq(o: "k"))
|
8
|
+
end
|
9
|
+
|
10
|
+
it "responds to a status check" do
|
11
|
+
response = HTTParty.get(url("/statusz"))
|
12
|
+
expect(response).to party_status(200)
|
13
|
+
expect(response).to party_response(include(:version))
|
14
|
+
end
|
15
|
+
end
|
data/lib/webhookdb/api/system.rb
CHANGED
@@ -13,7 +13,10 @@ class Webhookdb::API::System < Webhookdb::Service
|
|
13
13
|
helpers Webhookdb::Service::Helpers
|
14
14
|
|
15
15
|
get :healthz do
|
16
|
-
|
16
|
+
# Do not bother looking at dependencies like databases.
|
17
|
+
# If the primary is down, we can still accept webhooks
|
18
|
+
# if LoggedWebhook resiliency is configured,
|
19
|
+
# which is the primary thing about whether we're healthy or not.
|
17
20
|
status 200
|
18
21
|
{o: "k"}
|
19
22
|
end
|
@@ -29,9 +32,11 @@ class Webhookdb::API::System < Webhookdb::Service
|
|
29
32
|
}
|
30
33
|
end
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
if ["development", "test"].include?(Webhookdb::RACK_ENV)
|
36
|
+
resource :debug do
|
37
|
+
get :echo do
|
38
|
+
pp params.to_h
|
39
|
+
end
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
data/lib/webhookdb/apps.rb
CHANGED
@@ -34,6 +34,32 @@ require "webhookdb/admin_api/roles"
|
|
34
34
|
require "webterm/apps"
|
35
35
|
|
36
36
|
module Webhookdb::Apps
|
37
|
+
# Call this from your rackup file, like config.ru.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# lib = File.expand_path("lib", __dir__)
|
41
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
42
|
+
# require "webhookdb"
|
43
|
+
# Webhookdb.load_app
|
44
|
+
# require "webhookdb/apps"
|
45
|
+
# Webhookdb::Apps.rack_up(self)
|
46
|
+
#
|
47
|
+
def self.rack_up(config_ru)
|
48
|
+
Webhookdb::Async.setup_web
|
49
|
+
config_ru.instance_exec do
|
50
|
+
map "/admin" do
|
51
|
+
run Webhookdb::Apps::AdminAPI.build_app
|
52
|
+
end
|
53
|
+
map "/sidekiq" do
|
54
|
+
run Webhookdb::Apps::SidekiqWeb.to_app
|
55
|
+
end
|
56
|
+
map "/terminal" do
|
57
|
+
run Webhookdb::Apps::Webterm.to_app
|
58
|
+
end
|
59
|
+
run Webhookdb::Apps::API.build_app
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
37
63
|
class API < Webhookdb::Service
|
38
64
|
mount Webhookdb::API::Auth
|
39
65
|
mount Webhookdb::API::Db
|
@@ -9,7 +9,7 @@ module Webhookdb::Async::Autoscaler
|
|
9
9
|
include Appydays::Configurable
|
10
10
|
include Appydays::Loggable
|
11
11
|
|
12
|
-
AVAILABLE_PROVIDERS = ["heroku"].freeze
|
12
|
+
AVAILABLE_PROVIDERS = ["heroku", "fake"].freeze
|
13
13
|
|
14
14
|
def self._check_provider!
|
15
15
|
return if AVAILABLE_PROVIDERS.include?(self.provider)
|
@@ -26,6 +26,8 @@ module Webhookdb::Async::Autoscaler
|
|
26
26
|
setting :max_additional_workers, 2
|
27
27
|
setting :latency_restored_threshold, 0
|
28
28
|
setting :hostname_regex, /^web\.1$/, convert: ->(s) { Regexp.new(s) }
|
29
|
+
setting :heroku_app_id_or_app_name, "", key: "HEROKU_APP_NAME"
|
30
|
+
setting :heroku_formation_id_or_formation_type, "worker"
|
29
31
|
|
30
32
|
after_configured do
|
31
33
|
self._check_provider!
|
@@ -35,19 +37,25 @@ module Webhookdb::Async::Autoscaler
|
|
35
37
|
class << self
|
36
38
|
def enabled? = self.enabled
|
37
39
|
|
38
|
-
def
|
39
|
-
raise "already started" unless @instance.nil?
|
40
|
+
def build_implementation
|
40
41
|
case self.provider
|
41
42
|
when "heroku"
|
42
43
|
opts = {heroku: Webhookdb::Heroku.client, max_additional_workers: self.max_additional_workers}
|
43
44
|
(opts[:app_id_or_app_name] = self.heroku_app_id_or_app_name) if
|
44
|
-
self.heroku_app_id_or_app_name
|
45
|
+
self.heroku_app_id_or_app_name.present?
|
45
46
|
(opts[:formation_id_or_formation_type] = self.heroku_formation_id_or_formation_type) if
|
46
|
-
self.heroku_formation_id_or_formation_type
|
47
|
-
|
47
|
+
self.heroku_formation_id_or_formation_type.present?
|
48
|
+
return Amigo::Autoscaler::Heroku.new(**opts)
|
49
|
+
when "fake"
|
50
|
+
return FakeImplementation.new
|
48
51
|
else
|
49
52
|
self._check_provider!
|
50
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start
|
57
|
+
raise "already started" unless @instance.nil?
|
58
|
+
@impl = self.build_implementation
|
51
59
|
@instance = Amigo::Autoscaler.new(
|
52
60
|
poll_interval: self.poll_interval,
|
53
61
|
latency_threshold: self.latency_threshold,
|
@@ -81,4 +89,21 @@ module Webhookdb::Async::Autoscaler
|
|
81
89
|
self.logger.warn("high_latency_queues_resolved", depth:, duration:, scale_action:)
|
82
90
|
end
|
83
91
|
end
|
92
|
+
|
93
|
+
class FakeImplementation
|
94
|
+
attr_reader :scale_ups, :scale_downs
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@scale_ups = []
|
98
|
+
@scale_downs = []
|
99
|
+
end
|
100
|
+
|
101
|
+
def scale_up(*args)
|
102
|
+
@scale_ups << args
|
103
|
+
end
|
104
|
+
|
105
|
+
def scale_down(*args)
|
106
|
+
@scale_downs << args
|
107
|
+
end
|
108
|
+
end
|
84
109
|
end
|
data/lib/webhookdb/heroku.rb
CHANGED
@@ -14,7 +14,7 @@ class Webhookdb::Heroku
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.client
|
17
|
-
raise "
|
17
|
+
raise "WEBHOOKDB_HEROKU_OAUTH_TOKEN not set" if self.oauth_token.blank?
|
18
18
|
@client ||= PlatformAPI.connect_oauth(self.oauth_token)
|
19
19
|
return @client
|
20
20
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb"
|
4
|
+
|
5
|
+
module Webhookdb::Pry
|
6
|
+
# Call this from .pryrc.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# lib = File.expand_path("lib", __dir__)
|
10
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
11
|
+
#
|
12
|
+
# require "appydays/dotenviable"
|
13
|
+
# Appydays::Dotenviable.load
|
14
|
+
#
|
15
|
+
# require "webhookdb/pry"
|
16
|
+
# Webhookdb::Pry.setup(self)
|
17
|
+
def self.setup(main)
|
18
|
+
main.instance_exec do
|
19
|
+
require "pry/clipboard"
|
20
|
+
|
21
|
+
Pry.config.commands.alias_command "ch", "copy-history"
|
22
|
+
Pry.config.commands.alias_command "cr", "copy-result"
|
23
|
+
|
24
|
+
# Decode the given cookie string. Since cookies are encrypted,
|
25
|
+
# this is useful for debugging what they contain.
|
26
|
+
def decode_cookie(s)
|
27
|
+
require "webhookdb/service"
|
28
|
+
return Webhookdb::Service.decode_cookie(s)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Connect this session of Pry to the database.
|
32
|
+
# It also registers subscribers, so changes to the models are handled
|
33
|
+
# by their correct async jobs (since async jobs are handled in-process).
|
34
|
+
def connect
|
35
|
+
require "webhookdb"
|
36
|
+
Webhookdb.load_app
|
37
|
+
Webhookdb::Async.setup_web if Amigo.subscribers.empty?
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
def copt
|
42
|
+
rc = Appydays::Loggable[self].silence(:fatal) do
|
43
|
+
Webhookdb::Customer::ResetCode.order(:id).last
|
44
|
+
end
|
45
|
+
tok = rc.token
|
46
|
+
Clipboard.copy tok
|
47
|
+
puts "Copied OTP #{tok} for #{rc.customer.email} to clipboard"
|
48
|
+
return tok
|
49
|
+
end
|
50
|
+
|
51
|
+
# Load models and fixtures. Use this when riffing locally.
|
52
|
+
def repl
|
53
|
+
require "webhookdb"
|
54
|
+
Webhookdb.load_app
|
55
|
+
require "webhookdb/fixtures"
|
56
|
+
Webhookdb::Fixtures.load_all
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
def console
|
61
|
+
connect
|
62
|
+
require "webhookdb/console"
|
63
|
+
Webhookdb::Console.enable_safe_mode
|
64
|
+
self.extend Webhookdb::Console::MainMethods
|
65
|
+
Amigo.register_subscriber do |ev|
|
66
|
+
Webhookdb::Console.console_logger(ev)
|
67
|
+
end
|
68
|
+
return
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -3,9 +3,10 @@
|
|
3
3
|
# Write docs for docs.webhookdb.com Jekyll site.
|
4
4
|
class Webhookdb::Replicator::Docgen
|
5
5
|
def self.documentable_descriptors
|
6
|
-
return Webhookdb::Replicator.registry.
|
7
|
-
|
8
|
-
|
6
|
+
return Webhookdb::Replicator.registry.
|
7
|
+
values.
|
8
|
+
select(&:documentable?).
|
9
|
+
sort_by(&:name)
|
9
10
|
end
|
10
11
|
|
11
12
|
# @!attribute desc
|
data/lib/webhookdb/replicator.rb
CHANGED
@@ -79,6 +79,8 @@ class Webhookdb::Replicator
|
|
79
79
|
# Is this an enterprise-only replicator?
|
80
80
|
attr_reader :enterprise
|
81
81
|
|
82
|
+
def documentable? = @documentable
|
83
|
+
|
82
84
|
def initialize(
|
83
85
|
name:,
|
84
86
|
ctor:,
|
@@ -91,7 +93,8 @@ class Webhookdb::Replicator
|
|
91
93
|
api_docs_url: "",
|
92
94
|
description: nil,
|
93
95
|
enterprise: false,
|
94
|
-
documentation_url: nil
|
96
|
+
documentation_url: nil,
|
97
|
+
documentable: nil
|
95
98
|
)
|
96
99
|
raise ArgumentError, "must support one or both of webhooks and backfill" unless
|
97
100
|
supports_webhooks || supports_backfill
|
@@ -109,7 +112,7 @@ class Webhookdb::Replicator
|
|
109
112
|
@ctor = ctor.is_a?(Class) ? ctor.method(:new) : ctor
|
110
113
|
@resource_name_plural = resource_name_plural || "#{self.resource_name_singular}s"
|
111
114
|
@description = description || "Replicate #{self.resource_name_plural} into your database."
|
112
|
-
self.
|
115
|
+
@documentable = documentable.nil? ? !self.name.start_with?("webhookdb_", "fake_", "theranest_") : documentable
|
113
116
|
end
|
114
117
|
|
115
118
|
def inspect
|
@@ -26,12 +26,12 @@ module Webhookdb::IntegrationSpecHelpers
|
|
26
26
|
example.metadata[:integration]
|
27
27
|
|
28
28
|
@to_destroy = []
|
29
|
-
WebMock.allow_net_connect!
|
29
|
+
WebMock.allow_net_connect! if defined?(WebMock)
|
30
30
|
end
|
31
31
|
|
32
32
|
context.after(:each) do
|
33
33
|
@to_destroy.each(&:destroy)
|
34
|
-
WebMock.disable_net_connect!
|
34
|
+
WebMock.disable_net_connect! if defined?(WebMock)
|
35
35
|
end
|
36
36
|
super
|
37
37
|
end
|
@@ -22,6 +22,12 @@ module Webhookdb::Tasks
|
|
22
22
|
Rake::Task["specs:heroku_integration_step1"].invoke
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
desc "Print version info and exit"
|
27
|
+
task :version do
|
28
|
+
sha = Webhookdb::COMMIT[..8]
|
29
|
+
puts "#{sha} (#{Webhookdb::RELEASE}) - #{Webhookdb::RELEASE_CREATED_AT} - #{Webhookdb::RACK_ENV}"
|
30
|
+
end
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
@@ -36,7 +36,7 @@ module Webhookdb::Tasks
|
|
36
36
|
require "webhookdb/heroku"
|
37
37
|
Webhookdb::Heroku.client.dyno.create(
|
38
38
|
Webhookdb::Heroku.app_name,
|
39
|
-
command: "bundle exec rake specs:
|
39
|
+
command: "bundle exec rake specs:heroku_integration_step3",
|
40
40
|
env: {"INTEGRATION_TESTS" => "true"},
|
41
41
|
attach: false,
|
42
42
|
time_to_live: 10.minute.to_i,
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb"
|
4
|
+
|
5
|
+
module Webhookdb::Tasks
|
6
|
+
# Load all Webhookdb Rake tasks.
|
7
|
+
# You can also load them individually.
|
8
|
+
def self.load_all
|
9
|
+
require "webhookdb/tasks/admin"
|
10
|
+
Webhookdb::Tasks::Admin.new
|
11
|
+
require "webhookdb/tasks/annotate"
|
12
|
+
Webhookdb::Tasks::Annotate.new
|
13
|
+
require "webhookdb/tasks/db"
|
14
|
+
Webhookdb::Tasks::DB.new
|
15
|
+
require "webhookdb/tasks/docs"
|
16
|
+
Webhookdb::Tasks::Docs.new
|
17
|
+
require "webhookdb/tasks/fixture"
|
18
|
+
Webhookdb::Tasks::Fixture.new
|
19
|
+
require "webhookdb/tasks/release"
|
20
|
+
Webhookdb::Tasks::Release.new
|
21
|
+
require "webhookdb/tasks/message"
|
22
|
+
Webhookdb::Tasks::Message.new
|
23
|
+
require "webhookdb/tasks/regress"
|
24
|
+
Webhookdb::Tasks::Regress.new
|
25
|
+
require "webhookdb/tasks/sidekiq"
|
26
|
+
Webhookdb::Tasks::Sidekiq.new
|
27
|
+
require "webhookdb/tasks/specs"
|
28
|
+
Webhookdb::Tasks::Specs.new
|
29
|
+
end
|
30
|
+
end
|
data/lib/webhookdb/version.rb
CHANGED
data/lib/webhookdb.rb
CHANGED
@@ -22,6 +22,17 @@ Money.locale_backend = :i18n
|
|
22
22
|
Money.default_currency = "USD"
|
23
23
|
Money.rounding_mode = BigDecimal::ROUND_HALF_UP
|
24
24
|
|
25
|
+
module Appydays::Configurable
|
26
|
+
def self.fetch_env(keys, default=:__keyerror, env: ENV)
|
27
|
+
keys = [keys] unless keys.respond_to?(:to_ary)
|
28
|
+
keys.to_ary.each do |k|
|
29
|
+
return env.fetch(k) if env.key?(k)
|
30
|
+
end
|
31
|
+
raise KeyError, "no key found in env: #{keys}" if default == :__keyerror
|
32
|
+
return default
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
25
36
|
module Webhookdb
|
26
37
|
include Appydays::Loggable
|
27
38
|
include Appydays::Configurable
|
@@ -49,11 +60,15 @@ module Webhookdb
|
|
49
60
|
class RegressionModeSkip < StandardError; end
|
50
61
|
|
51
62
|
APPLICATION_NAME = "Webhookdb"
|
52
|
-
RACK_ENV =
|
53
|
-
COMMIT =
|
54
|
-
RELEASE =
|
55
|
-
RELEASE_CREATED_AT =
|
63
|
+
RACK_ENV = Appydays::Configurable.fetch_env(["RACK_ENV", "RUBY_ENV"], "development")
|
64
|
+
COMMIT = Appydays::Configurable.fetch_env(["COMMIT", "GIT_SHA", "HEROKU_SLUG_COMMIT"], "00000000")
|
65
|
+
RELEASE = Appydays::Configurable.fetch_env(["RELEASE", "GIT_REF", "HEROKU_RELEASE_VERSION"], "unknown-release")
|
66
|
+
RELEASE_CREATED_AT = Appydays::Configurable.fetch_env(
|
67
|
+
["RELEASE_CREATED_AT", "BUILT_AT", "HEROKU_RELEASE_CREATED_AT"],
|
68
|
+
Time.at(0).utc.iso8601,
|
69
|
+
)
|
56
70
|
INTEGRATION_TESTS_ENABLED = ENV.fetch("INTEGRATION_TESTS", false)
|
71
|
+
require "webhookdb/version"
|
57
72
|
|
58
73
|
DATA_DIR = Pathname(__FILE__).dirname.parent + "data"
|
59
74
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webhookdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- WebhookDB
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '1.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: clipboard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.3'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.3'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: concurrent-ruby
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -810,6 +824,12 @@ files:
|
|
810
824
|
- db/migrations/035_synchronous_backfill.rb
|
811
825
|
- db/migrations/036_oauth.rb
|
812
826
|
- db/migrations/037_oauth_used.rb
|
827
|
+
- integration/async_spec.rb
|
828
|
+
- integration/auth_spec.rb
|
829
|
+
- integration/database_spec.rb
|
830
|
+
- integration/helpers_spec.rb
|
831
|
+
- integration/service_integrations_spec.rb
|
832
|
+
- integration/system_spec.rb
|
813
833
|
- lib/amigo/durable_job.rb
|
814
834
|
- lib/pry/clipboard.rb
|
815
835
|
- lib/sequel/advisory_lock.rb
|
@@ -974,6 +994,7 @@ files:
|
|
974
994
|
- lib/webhookdb/postgres/testing_pixie.rb
|
975
995
|
- lib/webhookdb/postgres/validations.rb
|
976
996
|
- lib/webhookdb/postmark.rb
|
997
|
+
- lib/webhookdb/pry.rb
|
977
998
|
- lib/webhookdb/redis.rb
|
978
999
|
- lib/webhookdb/replicator.rb
|
979
1000
|
- lib/webhookdb/replicator/atom_single_feed_v1.rb
|
@@ -1081,6 +1102,7 @@ files:
|
|
1081
1102
|
- lib/webhookdb/stripe.rb
|
1082
1103
|
- lib/webhookdb/subscription.rb
|
1083
1104
|
- lib/webhookdb/sync_target.rb
|
1105
|
+
- lib/webhookdb/tasks.rb
|
1084
1106
|
- lib/webhookdb/tasks/admin.rb
|
1085
1107
|
- lib/webhookdb/tasks/annotate.rb
|
1086
1108
|
- lib/webhookdb/tasks/db.rb
|