webhookdb 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86109800d36b097754f33ad1319331d4de0c4f90345a062c7f3222e57ce020db
4
- data.tar.gz: 4ac7ca8c78b01ea0144f3c7b4ecba7f89bebd581a1eb119a580526d9c99a3c67
3
+ metadata.gz: '0285d7331a0f43ff14a35e0b578d011c4d330caa9a1b9acdc04d741bbd1f35ae'
4
+ data.tar.gz: 807c8366605a9de9ebd6f13502e88a892b077a1435793c5a3db6bc6b2c400dc2
5
5
  SHA512:
6
- metadata.gz: 98e8f1f6dc67c6be2921270bf8c577c2702cf438251606bb0dbf58cb24660b078d9b2e5c54e5f4c4794aa6b2bf168abefafd5be620c56d5d8b8ca37cea9e32a3
7
- data.tar.gz: 1f0800e477cb27e927debf7e7a05bb0eed867ff972f036eaaca733d8a3ca1927f7b73f1e006a49b85a79e5c781e8b8295b9c1c208773ae6df1790b08fada1076
6
+ metadata.gz: 89bf0867a274a37410d35de3ade1bef240898d518cf9377ff49acc006b56094221821e86c8ceda7ea2b2d6da68e004b58aff694c0ee778837f034f73cb0bbb22
7
+ data.tar.gz: 21cc2b77bb8779ceb7e451499a90b82cd6ac211108d52441bfc203ad1334001b4a02e0d1761506d8dee6fbd56ed4fe534eb151dda96da5aa49fddfbacfaf6464
@@ -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
@@ -13,7 +13,10 @@ class Webhookdb::API::System < Webhookdb::Service
13
13
  helpers Webhookdb::Service::Helpers
14
14
 
15
15
  get :healthz do
16
- Webhookdb::Postgres::Model.db.execute("SELECT 1=1")
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
- resource :debug do
33
- get :echo do
34
- pp params.to_h
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
@@ -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
@@ -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.values.reject do |repl|
7
- repl.name.start_with?("webhookdb_", "fake_")
8
- end.sort_by(&:name)
6
+ return Webhookdb::Replicator.registry.
7
+ values.
8
+ select(&:documentable?).
9
+ sort_by(&:name)
9
10
  end
10
11
 
11
12
  # @!attribute desc
@@ -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.feature_roles
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
@@ -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:integration_step3",
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Webhookdb
4
- VERSION = "0.1.1"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/webhookdb.rb CHANGED
@@ -54,6 +54,7 @@ module Webhookdb
54
54
  RELEASE = ENV.fetch("HEROKU_RELEASE_VERSION", "unknown-release")
55
55
  RELEASE_CREATED_AT = ENV.fetch("HEROKU_RELEASE_CREATED_AT") { Time.at(0).utc.iso8601 }
56
56
  INTEGRATION_TESTS_ENABLED = ENV.fetch("INTEGRATION_TESTS", false)
57
+ require "webhookdb/version"
57
58
 
58
59
  DATA_DIR = Pathname(__FILE__).dirname.parent + "data"
59
60
 
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.1
4
+ version: 1.0.0
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-08 00:00:00.000000000 Z
11
+ date: 2024-01-09 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