webhookdb 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/admin-dist/assets/index-6aebf805.js +264 -0
- data/admin-dist/favicon.ico +0 -0
- data/admin-dist/index.html +130 -0
- data/admin-dist/manifest.json +15 -0
- data/data/messages/replicators/url-recorder.liquid +20 -0
- data/data/messages/templates/errors/signalwire_send_sms.email.liquid +31 -0
- data/data/messages/web/install-customer-login.liquid +6 -5
- data/data/messages/web/install-error.liquid +1 -1
- data/data/messages/web/install-forbidden.liquid +25 -0
- data/data/messages/web/install-org-chooser.liquid +40 -0
- data/data/messages/web/install-success.liquid +2 -1
- data/data/messages/web/install.liquid +2 -1
- data/data/messages/web/partials/head.liquid +2 -0
- data/data/messages/web/styles.liquid +24 -0
- data/db/migrations/041_views.rb +20 -0
- data/db/migrations/042_sint_lock.rb +10 -0
- data/db/migrations/043_text_search.rb +28 -0
- data/db/migrations/044_oauth_session_token_cache.rb +21 -0
- data/integration/auth_spec.rb +2 -2
- data/lib/sequel/plugins/text_searchable.rb +165 -0
- data/lib/sequel/text_searchable.rb +42 -0
- data/lib/webhookdb/admin_api/auth.rb +24 -3
- data/lib/webhookdb/admin_api/data_provider.rb +196 -0
- data/lib/webhookdb/admin_api/entities.rb +143 -28
- data/lib/webhookdb/admin_api.rb +0 -2
- data/lib/webhookdb/api/auth.rb +5 -6
- data/lib/webhookdb/api/db.rb +31 -6
- data/lib/webhookdb/api/entities.rb +7 -1
- data/lib/webhookdb/api/helpers.rb +6 -25
- data/lib/webhookdb/api/install.rb +204 -79
- data/lib/webhookdb/api/organizations.rb +14 -12
- data/lib/webhookdb/api/saved_queries.rb +9 -3
- data/lib/webhookdb/api/saved_views.rb +99 -0
- data/lib/webhookdb/api/service_integrations.rb +15 -9
- data/lib/webhookdb/api/subscriptions.rb +3 -1
- data/lib/webhookdb/api/sync_targets.rb +9 -7
- data/lib/webhookdb/api/system.rb +1 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
- data/lib/webhookdb/apps.rb +30 -7
- data/lib/webhookdb/async/audit_logger.rb +2 -0
- data/lib/webhookdb/async.rb +5 -0
- data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
- data/lib/webhookdb/backfill_job.rb +9 -0
- data/lib/webhookdb/customer.rb +5 -0
- data/lib/webhookdb/database_document.rb +1 -1
- data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
- data/lib/webhookdb/db_adapter.rb +20 -4
- data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
- data/lib/webhookdb/fixtures/organizations.rb +5 -0
- data/lib/webhookdb/fixtures/roles.rb +14 -0
- data/lib/webhookdb/fixtures/saved_views.rb +25 -0
- data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
- data/lib/webhookdb/http.rb +8 -2
- data/lib/webhookdb/icalendar.rb +3 -0
- data/lib/webhookdb/idempotency.rb +69 -22
- data/lib/webhookdb/increase.rb +69 -21
- data/lib/webhookdb/intercom.rb +10 -3
- data/lib/webhookdb/jobs/backfill.rb +3 -1
- data/lib/webhookdb/jobs/emailer.rb +0 -1
- data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
- data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
- data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
- data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
- data/lib/webhookdb/message/body.rb +6 -4
- data/lib/webhookdb/message/delivery.rb +2 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
- data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
- data/lib/webhookdb/oauth/fake_provider.rb +44 -0
- data/lib/webhookdb/oauth/front_provider.rb +1 -2
- data/lib/webhookdb/oauth/increase_provider.rb +80 -0
- data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
- data/lib/webhookdb/oauth/session.rb +20 -0
- data/lib/webhookdb/oauth.rb +7 -21
- data/lib/webhookdb/organization/alerting.rb +2 -0
- data/lib/webhookdb/organization/database_migration.rb +3 -0
- data/lib/webhookdb/organization.rb +37 -6
- data/lib/webhookdb/organization_membership.rb +14 -7
- data/lib/webhookdb/postgres.rb +2 -0
- data/lib/webhookdb/replicator/base.rb +1 -0
- data/lib/webhookdb/replicator/docgen.rb +9 -1
- data/lib/webhookdb/replicator/fake.rb +2 -3
- data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
- data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
- data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
- data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
- data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
- data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
- data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
- data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
- data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
- data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
- data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
- data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
- data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
- data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
- data/lib/webhookdb/replicator/webhook_request.rb +4 -0
- data/lib/webhookdb/replicator.rb +8 -0
- data/lib/webhookdb/role.rb +5 -2
- data/lib/webhookdb/saved_query.rb +23 -0
- data/lib/webhookdb/saved_view.rb +73 -0
- data/lib/webhookdb/sentry.rb +2 -0
- data/lib/webhookdb/service/entities.rb +0 -4
- data/lib/webhookdb/service/helpers.rb +5 -0
- data/lib/webhookdb/service/middleware.rb +9 -0
- data/lib/webhookdb/service/types.rb +10 -8
- data/lib/webhookdb/service/validators.rb +1 -2
- data/lib/webhookdb/service/view_api.rb +1 -1
- data/lib/webhookdb/service_integration.rb +17 -15
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
- data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
- data/lib/webhookdb/subscription.rb +2 -0
- data/lib/webhookdb/sync_target.rb +10 -2
- data/lib/webhookdb/tasks/message.rb +3 -1
- data/lib/webhookdb/version.rb +1 -1
- data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
- data/lib/webhookdb/webhook_subscription.rb +2 -0
- metadata +57 -9
- data/lib/webhookdb/admin_api/customers.rb +0 -63
- data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
- data/lib/webhookdb/admin_api/roles.rb +0 -15
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
|
5
|
+
module SequelTextSearchable
|
6
|
+
VERSION = "0.0.1"
|
7
|
+
|
8
|
+
INDEX_MODES = [:async, :sync, :off].freeze
|
9
|
+
DEFAULT_MODE = :async
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def index_mode = @index_mode || DEFAULT_MODE
|
13
|
+
|
14
|
+
def index_mode=(v)
|
15
|
+
raise ArgumentError, "mode #{v.inspect} must be one of: #{INDEX_MODES}" unless
|
16
|
+
INDEX_MODES.include?(v)
|
17
|
+
@index_mode = v
|
18
|
+
end
|
19
|
+
|
20
|
+
def searchable_models = @searchable_models ||= []
|
21
|
+
|
22
|
+
# Return the global threadpool for :async indexing.
|
23
|
+
# Use at most a couple threads; if the work gets backed up,
|
24
|
+
# have the caller run it. If the threads die,
|
25
|
+
# the text update is lost, so we don't want to let it queue up forever.
|
26
|
+
def threadpool
|
27
|
+
return @threadpool ||= Concurrent::ThreadPoolExecutor.new(
|
28
|
+
min_threads: 1,
|
29
|
+
max_threads: 2,
|
30
|
+
max_queue: 10,
|
31
|
+
fallback_policy: :caller_runs,
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set your own threadpool.
|
36
|
+
attr_writer :threadpool
|
37
|
+
|
38
|
+
def reindex_all
|
39
|
+
return self.searchable_models.sum(&:text_search_reindex_all)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -8,7 +8,28 @@ class Webhookdb::AdminAPI::Auth < Webhookdb::AdminAPI::V1
|
|
8
8
|
resource :auth do
|
9
9
|
desc "Return the current administrator customer."
|
10
10
|
get do
|
11
|
-
present admin_customer, with: Webhookdb::AdminAPI::
|
11
|
+
present admin_customer, with: Webhookdb::AdminAPI::Entities::CurrentCustomer, env:
|
12
|
+
end
|
13
|
+
|
14
|
+
params do
|
15
|
+
requires :email, type: String
|
16
|
+
requires :password, type: String
|
17
|
+
end
|
18
|
+
|
19
|
+
resource :login do
|
20
|
+
auth(:skip)
|
21
|
+
params do
|
22
|
+
requires :email, type: String
|
23
|
+
requires :password, type: String
|
24
|
+
end
|
25
|
+
post do
|
26
|
+
(me = Webhookdb::Customer.with_email(params[:email])) or unauthenticated!
|
27
|
+
me.authenticate(params[:password]) or unauthenticated!
|
28
|
+
unauthenticated! unless me.admin?
|
29
|
+
set_customer(me)
|
30
|
+
status 200
|
31
|
+
present admin_customer, with: Webhookdb::AdminAPI::Entities::CurrentCustomer, env:
|
32
|
+
end
|
12
33
|
end
|
13
34
|
|
14
35
|
resource :impersonate do
|
@@ -17,7 +38,7 @@ class Webhookdb::AdminAPI::Auth < Webhookdb::AdminAPI::V1
|
|
17
38
|
Webhookdb::Service::Auth::Impersonation.new(env["warden"]).off(admin_customer)
|
18
39
|
|
19
40
|
status 200
|
20
|
-
present admin_customer, with: Webhookdb::AdminAPI::
|
41
|
+
present admin_customer, with: Webhookdb::AdminAPI::Entities::CurrentCustomer, env:
|
21
42
|
end
|
22
43
|
|
23
44
|
route_param :customer_id, type: Integer do
|
@@ -28,7 +49,7 @@ class Webhookdb::AdminAPI::Auth < Webhookdb::AdminAPI::V1
|
|
28
49
|
Webhookdb::Service::Auth::Impersonation.new(env["warden"]).on(target)
|
29
50
|
|
30
51
|
status 200
|
31
|
-
present target, with: Webhookdb::AdminAPI::
|
52
|
+
present target, with: Webhookdb::AdminAPI::Entities::CurrentCustomer, env:
|
32
53
|
end
|
33
54
|
end
|
34
55
|
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/admin_api"
|
4
|
+
|
5
|
+
class Webhookdb::AdminAPI::DataProvider < Webhookdb::AdminAPI::V1
|
6
|
+
include Webhookdb::AdminAPI::Entities
|
7
|
+
|
8
|
+
class CustomerRoleModel < Sequel::Model
|
9
|
+
many_to_one :customer, class: "Webhookdb::Customer"
|
10
|
+
many_to_one :role, class: "Webhookdb::Role"
|
11
|
+
end
|
12
|
+
CustomerRoleModel.set_dataset(Webhookdb::Postgres::Model.db[:roles_customers])
|
13
|
+
|
14
|
+
TYPEINFO = {
|
15
|
+
backfill_jobs: [Webhookdb::BackfillJob, BackfillJob],
|
16
|
+
customers: [Webhookdb::Customer, Customer],
|
17
|
+
customer_reset_codes: [Webhookdb::Customer::ResetCode, CustomerResetCode],
|
18
|
+
customer_roles: [CustomerRoleModel, CustomerRole],
|
19
|
+
logged_webhooks: [Webhookdb::LoggedWebhook, LoggedWebhook],
|
20
|
+
message_bodies: [Webhookdb::Message::Body, MessageBody],
|
21
|
+
message_deliveries: [Webhookdb::Message::Delivery, MessageDelivery],
|
22
|
+
organization_database_migrations: [Webhookdb::Organization::DatabaseMigration, OrganizationDatabaseMigration],
|
23
|
+
organization_memberships: [Webhookdb::OrganizationMembership, OrganizationMembership],
|
24
|
+
organizations: [Webhookdb::Organization, Organization],
|
25
|
+
replicated_databases: [nil, nil],
|
26
|
+
roles: [Webhookdb::Role, Role],
|
27
|
+
saved_queries: [Webhookdb::SavedQuery, SavedQuery],
|
28
|
+
saved_views: [Webhookdb::SavedView, SavedView],
|
29
|
+
service_integrations: [Webhookdb::ServiceIntegration, ServiceIntegration],
|
30
|
+
subscriptions: [Webhookdb::Subscription, Subscription],
|
31
|
+
sync_targets: [Webhookdb::SyncTarget, SyncTarget],
|
32
|
+
webhook_subscriptions: [Webhookdb::WebhookSubscription, WebhookSubscription],
|
33
|
+
webhook_subscription_deliveries: [Webhookdb::WebhookSubscription::Delivery, WebhookSubscriptionDelivery],
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
TYPES_FOR_RESOURCES = TYPEINFO.transform_values { |v| v[0] }.freeze
|
37
|
+
ENTITIES_FOR_TYPES = TYPEINFO.values.to_h { |v| [v[0], v[1]] }.freeze
|
38
|
+
|
39
|
+
resource :data_provider do
|
40
|
+
helpers do
|
41
|
+
params :data_provider_pagination do
|
42
|
+
requires :page, type: Integer
|
43
|
+
requires :per_page, type: Integer
|
44
|
+
end
|
45
|
+
|
46
|
+
params :data_provider_sort do
|
47
|
+
requires :field, type: Symbol
|
48
|
+
requires :order, type: Symbol, values: [:ASC, :DESC]
|
49
|
+
end
|
50
|
+
|
51
|
+
params :base_params do
|
52
|
+
requires :resource, type: Symbol, values: TYPES_FOR_RESOURCES.keys
|
53
|
+
optional :meta, type: JSON
|
54
|
+
end
|
55
|
+
|
56
|
+
params :base_record_params do
|
57
|
+
use :base_params
|
58
|
+
requires :id, type: Integer
|
59
|
+
end
|
60
|
+
|
61
|
+
params :list_params do
|
62
|
+
use :base_params
|
63
|
+
optional :pagination, type: JSON do
|
64
|
+
use :data_provider_pagination
|
65
|
+
end
|
66
|
+
optional :sort, type: JSON do
|
67
|
+
use :data_provider_sort
|
68
|
+
end
|
69
|
+
optional :filter, type: JSON
|
70
|
+
end
|
71
|
+
|
72
|
+
params :many_ids_params do
|
73
|
+
use :base_params
|
74
|
+
requires :ids, type: [Integer]
|
75
|
+
end
|
76
|
+
|
77
|
+
def lookup_model_type
|
78
|
+
rt = TYPES_FOR_RESOURCES.fetch(params.fetch(:resource))
|
79
|
+
return rt
|
80
|
+
end
|
81
|
+
|
82
|
+
def lookup_model
|
83
|
+
cls = lookup_model_type
|
84
|
+
m = cls[id: params.fetch(:id)]
|
85
|
+
merror!(403, "No #{params[:resource]} with pk #{params[:id]}", code: "forbidden") if m.nil?
|
86
|
+
return m
|
87
|
+
end
|
88
|
+
|
89
|
+
def lookup_entity(cls)
|
90
|
+
return ENTITIES_FOR_TYPES.fetch(cls)
|
91
|
+
end
|
92
|
+
|
93
|
+
def present_one(item, item_entity)
|
94
|
+
data = item_entity.represent(item)
|
95
|
+
e = {data:}
|
96
|
+
present e
|
97
|
+
end
|
98
|
+
|
99
|
+
def present_dataset(ds, item_entity)
|
100
|
+
data = ds.all.map { |o| item_entity.represent(o) }
|
101
|
+
total = ds.respond_to?(:pagination_record_count) ? ds.pagination_record_count : ds.limit(nil).offset(nil).count
|
102
|
+
e = {data:, total:}
|
103
|
+
present e
|
104
|
+
end
|
105
|
+
|
106
|
+
def apply_list_params(ods)
|
107
|
+
ds = ods
|
108
|
+
if (filter = params[:filter])
|
109
|
+
search_term = filter.delete(:q)
|
110
|
+
ds = ds.text_search(search_term) if search_term && ds.respond_to?(:text_search)
|
111
|
+
ds = ds.where(filter.to_h.transform_keys(&:to_sym)) if filter.present?
|
112
|
+
end
|
113
|
+
if (sort = params[:sort])
|
114
|
+
sort_col = sort[:field]
|
115
|
+
unless sort_col.to_s.include?(".")
|
116
|
+
# If the sort column includes a '.', we have to special case it.
|
117
|
+
# Right now we don't have any allowed sort cols.
|
118
|
+
# It is possible to make an unsupported column sortable in react-admin, causing the backend to error,
|
119
|
+
# but it's not easy to clear out your filters/sort settings once this happens.
|
120
|
+
# So instead, just avoid causing the error, and just no-op if an invalid column is passed in.
|
121
|
+
order = Sequel.send(sort[:order].to_s.downcase, sort_col, nulls: :last)
|
122
|
+
ds = ds.order(order)
|
123
|
+
end
|
124
|
+
else
|
125
|
+
ds = ds.order(Sequel.desc(ds.model.primary_key))
|
126
|
+
end
|
127
|
+
if (pagination = params[:pagination])
|
128
|
+
ds = ds.paginate(pagination[:page], pagination[:per_page])
|
129
|
+
end
|
130
|
+
return ds
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
params do
|
135
|
+
use :base_record_params
|
136
|
+
end
|
137
|
+
post :get_one do
|
138
|
+
model = lookup_model
|
139
|
+
status 200
|
140
|
+
present_one model, lookup_entity(model.class)
|
141
|
+
end
|
142
|
+
|
143
|
+
params do
|
144
|
+
use :list_params
|
145
|
+
end
|
146
|
+
post :get_list do
|
147
|
+
model_cls = lookup_model_type
|
148
|
+
ds = model_cls.dataset
|
149
|
+
ds = apply_list_params(ds)
|
150
|
+
status 200
|
151
|
+
present_dataset ds, lookup_entity(model_cls)
|
152
|
+
end
|
153
|
+
|
154
|
+
params do
|
155
|
+
use :many_ids_params
|
156
|
+
end
|
157
|
+
post :get_many do
|
158
|
+
model_cls = lookup_model_type
|
159
|
+
ds = model_cls.dataset
|
160
|
+
ds = ds.where(id: params.fetch(:ids))
|
161
|
+
status 200
|
162
|
+
present_dataset ds, lookup_entity(model_cls)
|
163
|
+
end
|
164
|
+
|
165
|
+
params do
|
166
|
+
use :list_params
|
167
|
+
requires :target, type: Symbol
|
168
|
+
requires :id, type: Integer
|
169
|
+
end
|
170
|
+
post :get_many_reference do
|
171
|
+
if params[:resource] == :replicated_databases
|
172
|
+
(org = Webhookdb::Organization[params[:id]]) or forbidden!
|
173
|
+
rows = org.admin_connection do |db|
|
174
|
+
rows = db[Sequel[:information_schema][:tables]].
|
175
|
+
where(table_schema: org.replication_schema).
|
176
|
+
select(
|
177
|
+
Sequel[:table_name].as(:id),
|
178
|
+
:table_name,
|
179
|
+
Sequel.expr { pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) }.as(:size_pretty),
|
180
|
+
Sequel.expr { pg_relation_size(quote_ident(table_name)) }.as(:size),
|
181
|
+
).all
|
182
|
+
rows
|
183
|
+
end
|
184
|
+
status 200
|
185
|
+
present({data: rows, total: rows.length})
|
186
|
+
else
|
187
|
+
model_cls = lookup_model_type
|
188
|
+
ds = model_cls.dataset
|
189
|
+
ds = ds.where(params[:target] => params[:id])
|
190
|
+
ds = apply_list_params(ds)
|
191
|
+
status 200
|
192
|
+
present_dataset ds, lookup_entity(model_cls)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -5,62 +5,177 @@ require "grape_entity"
|
|
5
5
|
require "webhookdb/service/entities"
|
6
6
|
require "webhookdb/admin_api" unless defined? Webhookdb::AdminAPI
|
7
7
|
|
8
|
-
module Webhookdb::AdminAPI
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
module Webhookdb::AdminAPI::Entities
|
9
|
+
CurrentCustomer = Webhookdb::Service::Entities::CurrentCustomer
|
10
|
+
Money = Webhookdb::Service::Entities::Money
|
11
|
+
TimeRange = Webhookdb::Service::Entities::TimeRange
|
12
12
|
|
13
|
-
class
|
13
|
+
class Base < Webhookdb::Service::Entities::Base
|
14
|
+
expose :id, if: ->(o) { o.respond_to?(:id) }
|
15
|
+
expose :created_at, if: ->(o) { o.respond_to?(:created_at) }
|
16
|
+
expose :updated_at, if: ->(o) { o.respond_to?(:updated_at) }
|
17
|
+
expose :soft_deleted_at, if: ->(o) { o.respond_to?(:soft_deleted_at) }
|
18
|
+
expose :admin_link, if: ->(o, _) { o.respond_to?(:admin_link) }
|
19
|
+
end
|
14
20
|
|
15
|
-
class
|
21
|
+
class Related < Grape::Entity
|
16
22
|
expose :id
|
17
|
-
expose :name
|
18
23
|
end
|
19
24
|
|
20
|
-
class
|
21
|
-
expose :
|
22
|
-
expose :
|
25
|
+
class BackfillJob < Base
|
26
|
+
expose :service_integration, with: Related
|
27
|
+
expose :organization, with: Related, &self.delegate_to(:service_integration, :organization)
|
28
|
+
expose :started_at
|
29
|
+
expose :finished_at
|
30
|
+
expose :opaque_id
|
31
|
+
expose :parent_job, with: Related
|
32
|
+
expose :created_by, with: Related
|
33
|
+
expose :incremental
|
34
|
+
end
|
35
|
+
|
36
|
+
class Customer < Base
|
23
37
|
expose :email
|
24
38
|
expose :name
|
25
39
|
expose :note
|
26
40
|
end
|
27
41
|
|
28
|
-
class
|
29
|
-
expose :id
|
30
|
-
expose :created_at
|
42
|
+
class CustomerResetCode < Base
|
31
43
|
expose :transport
|
32
44
|
expose :token
|
33
45
|
expose :used
|
34
46
|
expose :expire_at
|
47
|
+
expose :customer, with: Related
|
35
48
|
end
|
36
49
|
|
37
|
-
class
|
38
|
-
expose :
|
39
|
-
|
50
|
+
class CustomerRole < Base
|
51
|
+
expose :customer do
|
52
|
+
expose :id, &self.delegate_to(:customer_id)
|
53
|
+
expose :email, &self.delegate_to(:customer, :email)
|
54
|
+
end
|
55
|
+
expose :role do
|
56
|
+
expose :id, &self.delegate_to(:role_id)
|
57
|
+
expose :name, &self.delegate_to(:role, :name)
|
40
58
|
end
|
41
|
-
expose :reset_codes, with: CustomerResetCodes
|
42
59
|
end
|
43
60
|
|
44
|
-
class
|
45
|
-
expose :
|
61
|
+
class LoggedWebhook < Base
|
62
|
+
expose :inserted_at
|
63
|
+
expose :truncated_at
|
64
|
+
expose :request_body
|
65
|
+
expose(:request_headers) { |inst| inst.request_headers.to_a }
|
66
|
+
expose :response_status
|
67
|
+
expose :service_integration_opaque_id
|
68
|
+
expose :service_integration, with: Related
|
69
|
+
expose :organization, with: Related
|
70
|
+
expose :request_method
|
71
|
+
expose :request_path
|
72
|
+
end
|
73
|
+
|
74
|
+
class MessageBody < Base
|
46
75
|
expose :content
|
47
76
|
expose :mediatype
|
77
|
+
expose :delivery, with: Related
|
48
78
|
end
|
49
79
|
|
50
|
-
class
|
51
|
-
expose :
|
52
|
-
expose :
|
53
|
-
expose :
|
54
|
-
expose :soft_deleted_at
|
80
|
+
class MessageDelivery < Base
|
81
|
+
expose :extra_fields
|
82
|
+
expose :recipient, with: Related
|
83
|
+
expose :sent_at
|
55
84
|
expose :template
|
85
|
+
expose :to
|
56
86
|
expose :transport_type
|
57
87
|
expose :transport_service
|
58
88
|
expose :transport_message_id
|
59
|
-
expose :sent_at
|
60
|
-
expose :to
|
61
89
|
end
|
62
90
|
|
63
|
-
class
|
64
|
-
expose :
|
91
|
+
class Organization < Base
|
92
|
+
expose :name
|
93
|
+
expose :key
|
94
|
+
end
|
95
|
+
|
96
|
+
class OrganizationDatabaseMigration < Base
|
97
|
+
expose :organization, with: Related
|
98
|
+
expose :started_at
|
99
|
+
expose :finished_at
|
100
|
+
expose :started_by, with: Related
|
101
|
+
expose :organization_schema
|
102
|
+
expose :last_migrated_service_integration_id
|
103
|
+
expose :last_migrated_service_integration, with: Related
|
104
|
+
expose :last_migrated_timestamp
|
105
|
+
end
|
106
|
+
|
107
|
+
class OrganizationMembership < Base
|
108
|
+
expose :organization, with: Related
|
109
|
+
expose :customer, with: Related
|
110
|
+
expose :membership_role, with: Related
|
111
|
+
expose :verified
|
112
|
+
expose :invitation_code
|
113
|
+
expose :is_default
|
114
|
+
end
|
115
|
+
|
116
|
+
class Role < Base
|
117
|
+
expose :name
|
118
|
+
end
|
119
|
+
|
120
|
+
class SavedQuery < Base
|
121
|
+
expose :organization, with: Related
|
122
|
+
expose :created_by, with: Related
|
123
|
+
expose :opaque_id
|
124
|
+
expose :description
|
125
|
+
expose :sql
|
126
|
+
expose :public
|
127
|
+
end
|
128
|
+
|
129
|
+
class SavedView < Base
|
130
|
+
expose :organization, with: Related
|
131
|
+
expose :created_by, with: Related
|
132
|
+
expose :name
|
133
|
+
expose :sql
|
134
|
+
end
|
135
|
+
|
136
|
+
class ServiceIntegration < Base
|
137
|
+
expose :organization, with: Related
|
138
|
+
expose :table_name
|
139
|
+
expose :service_name
|
140
|
+
expose :opaque_id
|
141
|
+
expose :last_backfilled_at
|
142
|
+
expose :depends_on, with: Related
|
143
|
+
expose :skip_webhook_verification
|
144
|
+
end
|
145
|
+
|
146
|
+
class Subscription < Base
|
147
|
+
expose :organization, with: Related
|
148
|
+
expose :stripe_id
|
149
|
+
expose :stripe_customer_id
|
150
|
+
expose :stripe_json
|
151
|
+
end
|
152
|
+
|
153
|
+
class SyncTarget < Base
|
154
|
+
expose :organization, with: Related
|
155
|
+
expose :service_integration, with: Related
|
156
|
+
expose :created_by, with: Related
|
157
|
+
expose :opaque_id
|
158
|
+
expose :period_seconds
|
159
|
+
expose :schema
|
160
|
+
expose :table
|
161
|
+
expose :last_synced_at
|
162
|
+
expose :last_applied_schema
|
163
|
+
expose :page_size
|
164
|
+
end
|
165
|
+
|
166
|
+
class WebhookSubscription < Base
|
167
|
+
expose :organization, with: Related
|
168
|
+
expose :service_integration, with: Related
|
169
|
+
expose :created_by, with: Related
|
170
|
+
expose :opaque_id
|
171
|
+
expose :deliver_to_url
|
172
|
+
expose :deactivated_at
|
173
|
+
end
|
174
|
+
|
175
|
+
class WebhookSubscriptionDelivery < Base
|
176
|
+
expose :attempt_timestamps
|
177
|
+
expose :attempt_http_response_statuses
|
178
|
+
expose :payload
|
179
|
+
expose :webhook_subscription, with: Related
|
65
180
|
end
|
66
181
|
end
|
data/lib/webhookdb/admin_api.rb
CHANGED
data/lib/webhookdb/api/auth.rb
CHANGED
@@ -26,13 +26,12 @@ class Webhookdb::API::Auth < Webhookdb::API::V1
|
|
26
26
|
|
27
27
|
params do
|
28
28
|
optional :email,
|
29
|
-
type:
|
30
|
-
coerce_with: NormalizedEmail,
|
29
|
+
type: NormalizedEmail,
|
31
30
|
prompt: {
|
32
31
|
message: "Welcome to WebhookDB!\nPlease enter your email:",
|
33
32
|
demo_mode_proc: ->(r) { r.params[:email] = "demo@webhookdb.com" },
|
34
33
|
}
|
35
|
-
optional :token, type:
|
34
|
+
optional :token, type: TrimmedString
|
36
35
|
end
|
37
36
|
post do
|
38
37
|
message = ""
|
@@ -86,9 +85,9 @@ class Webhookdb::API::Auth < Webhookdb::API::V1
|
|
86
85
|
end
|
87
86
|
|
88
87
|
params do
|
89
|
-
requires :form_name, type:
|
90
|
-
optional :email, type:
|
91
|
-
optional :name, type:
|
88
|
+
requires :form_name, type: TrimmedString
|
89
|
+
optional :email, type: TrimmedString
|
90
|
+
optional :name, type: TrimmedString
|
92
91
|
optional :message, type: String
|
93
92
|
end
|
94
93
|
post :contact do
|
data/lib/webhookdb/api/db.rb
CHANGED
@@ -6,15 +6,17 @@ require "webhookdb/api"
|
|
6
6
|
require "webhookdb/replicator"
|
7
7
|
|
8
8
|
class Webhookdb::API::Db < Webhookdb::API::V1
|
9
|
+
include Webhookdb::Service::Types
|
10
|
+
|
9
11
|
helpers do
|
10
12
|
params :fdw do
|
11
13
|
optional :message_fdw, type: Boolean
|
12
14
|
optional :message_views, type: Boolean
|
13
15
|
optional :message_all, type: Boolean
|
14
|
-
requires :remote_server_name, type:
|
15
|
-
requires :fetch_size, type:
|
16
|
-
requires :local_schema, type:
|
17
|
-
requires :view_schema, type:
|
16
|
+
requires :remote_server_name, type: TrimmedString
|
17
|
+
requires :fetch_size, type: TrimmedString
|
18
|
+
requires :local_schema, type: TrimmedString
|
19
|
+
requires :view_schema, type: TrimmedString
|
18
20
|
end
|
19
21
|
|
20
22
|
def run_fdw
|
@@ -38,6 +40,29 @@ class Webhookdb::API::Db < Webhookdb::API::V1
|
|
38
40
|
end
|
39
41
|
|
40
42
|
resource :db do
|
43
|
+
[:get, :post].each do |httpmethod|
|
44
|
+
desc "Execute an arbitrary query in a replication database. Same as /db/<org>/sql but safe for CORS usage." do
|
45
|
+
headers Webhookdb::API::ConnstrAuth.headers_desc
|
46
|
+
end
|
47
|
+
params do
|
48
|
+
requires :org_identifier, type: TrimmedString
|
49
|
+
optional :query, type: String, desc: "SQL to run."
|
50
|
+
optional :query_base64, type: TrimmedString, desc: "Base64 encoded SQL. Mostly used for GET requests."
|
51
|
+
exactly_one_of :query, :query_base64
|
52
|
+
end
|
53
|
+
send(httpmethod, :run_sql) do
|
54
|
+
use_http_expires_caching(5.minutes)
|
55
|
+
org = lookup_org!(allow_connstr_auth: true)
|
56
|
+
unless (query = params[:query])
|
57
|
+
query = Base64.urlsafe_decode64(params[:query_base64])
|
58
|
+
end
|
59
|
+
r, msg = execute_readonly_query(org, query)
|
60
|
+
merror!(403, msg, code: "invalid_query") if r.nil?
|
61
|
+
status 200
|
62
|
+
present({rows: r.rows, headers: r.columns, max_rows_reached: r.max_rows_reached})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
41
66
|
route_param :org_identifier, type: String do
|
42
67
|
desc "Returns the connection string"
|
43
68
|
get :connection do
|
@@ -64,7 +89,7 @@ class Webhookdb::API::Db < Webhookdb::API::V1
|
|
64
89
|
|
65
90
|
desc "Enqueues a database migration."
|
66
91
|
params do
|
67
|
-
optional :admin_url, type:
|
92
|
+
optional :admin_url, type: TrimmedString, prompt: {
|
68
93
|
message: "ADMIN Postgres connection URL, in the form 'postgres://user:password@host:port/dbname',\n" \
|
69
94
|
"that is capable of administrative operations on your database,\n" \
|
70
95
|
"such as creating and dropping schemas and tables.\n" \
|
@@ -73,7 +98,7 @@ class Webhookdb::API::Db < Webhookdb::API::V1
|
|
73
98
|
disable: ->(_) { !Webhookdb::Organization::DbBuilder.allow_public_migrations },
|
74
99
|
}
|
75
100
|
|
76
|
-
optional :readonly_url, type:
|
101
|
+
optional :readonly_url, type: TrimmedString, prompt: {
|
77
102
|
message: "READONLY Postgres connection URL.\n" \
|
78
103
|
"This string is displayed when you ask for your organization's connection information.\n" \
|
79
104
|
"If you are okay with this being your ADMIN URL, leave it blank.\n" \
|
@@ -9,7 +9,11 @@ module Webhookdb::API
|
|
9
9
|
MoneyEntity = Webhookdb::Service::Entities::Money
|
10
10
|
TimeRangeEntity = Webhookdb::Service::Entities::TimeRange
|
11
11
|
|
12
|
-
class BaseEntity < Webhookdb::Service::Entities::Base
|
12
|
+
class BaseEntity < Webhookdb::Service::Entities::Base
|
13
|
+
expose :message do |_instance, options|
|
14
|
+
options[:message] || ""
|
15
|
+
end
|
16
|
+
end
|
13
17
|
|
14
18
|
class OrganizationEntity < BaseEntity
|
15
19
|
expose :id
|
@@ -43,11 +47,13 @@ module Webhookdb::API
|
|
43
47
|
expose :verified_memberships, with: OrganizationMembershipEntity
|
44
48
|
expose :verified_memberships_formatted do |instance|
|
45
49
|
lines = instance.verified_memberships.map { |m| "#{m.organization.display_string}: #{m.status}" }
|
50
|
+
lines.sort!
|
46
51
|
lines.join("\n")
|
47
52
|
end
|
48
53
|
expose :invited_memberships, as: :invitations, with: OrganizationMembershipEntity
|
49
54
|
expose :invitations_formatted do |instance|
|
50
55
|
lines = instance.invited_memberships.map { |m| "#{m.organization.display_string}: #{m.invitation_code}" }
|
56
|
+
lines.sort!
|
51
57
|
lines.join("\n")
|
52
58
|
end
|
53
59
|
expose :display_headers do |_|
|
@@ -164,15 +164,15 @@ module Webhookdb::API::Helpers
|
|
164
164
|
request_headers = {}
|
165
165
|
raise LocalJumpError unless block_given?
|
166
166
|
begin
|
167
|
+
request_headers = request.headers.dup
|
168
|
+
if (content_type = env["CONTENT_TYPE"])
|
169
|
+
request_headers["Content-Type"] = content_type
|
170
|
+
end
|
167
171
|
sint = yield
|
168
172
|
return if sint == :pass
|
169
173
|
raise "error instead of return nil if there is no service integration" if sint.nil?
|
170
174
|
opaque_id = sint.opaque_id
|
171
175
|
organization_id = sint.organization_id
|
172
|
-
request_headers = request.headers.dup
|
173
|
-
if (content_type = env["CONTENT_TYPE"])
|
174
|
-
request_headers["Content-Type"] = content_type
|
175
|
-
end
|
176
176
|
svc = Webhookdb::Replicator.create(sint).dispatch_request_to(request)
|
177
177
|
svc.preprocess_headers_for_logging(request_headers)
|
178
178
|
handling_sint = svc.service_integration
|
@@ -205,6 +205,7 @@ module Webhookdb::API::Helpers
|
|
205
205
|
path: process_kwargs[:request_path],
|
206
206
|
headers: process_kwargs[:headers],
|
207
207
|
body: process_kwargs[:body],
|
208
|
+
rack_request: request,
|
208
209
|
)
|
209
210
|
inserted = svc.upsert_webhook(whreq)
|
210
211
|
s_body = svc.synchronous_processing_response_body(upserted: inserted, request: whreq)
|
@@ -256,25 +257,5 @@ module Webhookdb::API::Helpers
|
|
256
257
|
# On query success, return <QueryResult, nil>.
|
257
258
|
# On DatabaseError, return <nil, message>.
|
258
259
|
# On other types of errors, raise.
|
259
|
-
def execute_readonly_query(org, sql)
|
260
|
-
result = org.execute_readonly_query(sql)
|
261
|
-
return result, nil
|
262
|
-
rescue Sequel::DatabaseError => e
|
263
|
-
self.logger.error("db_query_database_error", error: e)
|
264
|
-
# We want to handle InsufficientPrivileges and UndefinedTable explicitly
|
265
|
-
# since we can hint the user at what to do.
|
266
|
-
# Otherwise, we should just return the Postgres exception.
|
267
|
-
msg = ""
|
268
|
-
case e.wrapped_exception
|
269
|
-
when PG::UndefinedTable
|
270
|
-
missing_table = e.wrapped_exception.message.match(/relation (.+) does not/)&.captures&.first
|
271
|
-
msg = "The table #{missing_table} does not exist. Run `webhookdb db tables` to see available tables." if
|
272
|
-
missing_table
|
273
|
-
when PG::InsufficientPrivilege
|
274
|
-
msg = "You do not have permission to perform this query. Queries must be read-only."
|
275
|
-
else
|
276
|
-
msg = e.wrapped_exception.message
|
277
|
-
end
|
278
|
-
return [nil, msg]
|
279
|
-
end
|
260
|
+
def execute_readonly_query(org, sql) = org.execute_readonly_query_with_help(sql)
|
280
261
|
end
|