webhookdb 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|