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
|
@@ -12,12 +12,16 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
12
12
|
helpers do
|
|
13
13
|
def lookup_session!
|
|
14
14
|
session = Webhookdb::Oauth::Session.usable.where(oauth_state: params[:state]).first
|
|
15
|
-
|
|
15
|
+
error!("Forbidden", 302, {"Location" => "/v1/install/#{oauth_provider.key}/forbidden"}) if session.nil?
|
|
16
16
|
return session
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def handle_login(email:, session:, action_url:)
|
|
20
|
-
|
|
20
|
+
begin
|
|
21
|
+
new_customer, me = Webhookdb::Customer.find_or_create_for_email(email)
|
|
22
|
+
rescue Sequel::ValidationFailed => e
|
|
23
|
+
raise FormError.new(e.message.capitalize, 400)
|
|
24
|
+
end
|
|
21
25
|
me.reset_codes_dataset.usable.each(&:expire!)
|
|
22
26
|
me.add_reset_code(transport: "email")
|
|
23
27
|
session.update(customer: me)
|
|
@@ -25,6 +29,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
25
29
|
"messages/web/install-customer-login.liquid",
|
|
26
30
|
serialize_view_params: true,
|
|
27
31
|
vars: {
|
|
32
|
+
app_name: oauth_provider.app_name,
|
|
28
33
|
view: "otp",
|
|
29
34
|
action_url:,
|
|
30
35
|
oauth_state: session.oauth_state,
|
|
@@ -50,47 +55,32 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
50
55
|
end
|
|
51
56
|
end
|
|
52
57
|
|
|
58
|
+
params do
|
|
59
|
+
requires :state, type: String
|
|
60
|
+
end
|
|
61
|
+
get :fake_oauth_authorization do
|
|
62
|
+
redirect "/v1/install/fake/callback?code=fakecode&state=#{params[:state]}"
|
|
63
|
+
end
|
|
64
|
+
|
|
53
65
|
route_param :oauth_provider, type: String, values: Webhookdb::Oauth.registry.keys do
|
|
54
66
|
helpers do
|
|
55
67
|
def oauth_provider
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def finish_org_setup(organization:, tokens:, scope:)
|
|
60
|
-
organization.prepare_database_connections?
|
|
61
|
-
oauth_provider.build_marketplace_integrations(organization:, tokens:, scope:)
|
|
62
|
-
rendered = render_liquid(
|
|
63
|
-
"messages/web/install-success.liquid",
|
|
64
|
-
serialize_view_params: true,
|
|
65
|
-
vars: {
|
|
66
|
-
app_name: oauth_provider.app_name,
|
|
67
|
-
database_url: organization.readonly_connection_url,
|
|
68
|
-
supports_webhooks: oauth_provider.supports_webhooks?,
|
|
69
|
-
},
|
|
70
|
-
)
|
|
71
|
-
status 200
|
|
72
|
-
body rendered
|
|
68
|
+
@oauth_provider ||= Webhookdb::Oauth.provider(params[:oauth_provider])
|
|
69
|
+
rescue KeyError
|
|
70
|
+
forbidden!
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def exchange_authorization_code(code)
|
|
76
74
|
return oauth_provider.exchange_authorization_code(code:)
|
|
77
75
|
rescue Webhookdb::Http::Error => e
|
|
78
76
|
logger.warn "oauth_exchange_error", exception: e
|
|
77
|
+
url = "#{Webhookdb.api_url}/v1/install/#{oauth_provider.key}"
|
|
79
78
|
raise FormError.new(
|
|
80
|
-
"Something went wrong getting your access token from #{oauth_provider.app_name}.
|
|
79
|
+
"Something went wrong getting your access token from #{oauth_provider.app_name}. " \
|
|
80
|
+
"Please start over by going to <a href=\"#{url}\">#{url}</a>.",
|
|
81
81
|
400,
|
|
82
82
|
)
|
|
83
83
|
end
|
|
84
|
-
|
|
85
|
-
def find_admin_membership(customer)
|
|
86
|
-
_created, membership = Webhookdb::Customer.find_or_create_default_organization(customer)
|
|
87
|
-
membership = customer.verified_memberships.find(&:admin?) unless membership.admin?
|
|
88
|
-
return membership if membership
|
|
89
|
-
raise FormError.new(
|
|
90
|
-
"You must be an administrator of your WebhookDB organization to set up this app.",
|
|
91
|
-
403,
|
|
92
|
-
)
|
|
93
|
-
end
|
|
94
84
|
end
|
|
95
85
|
|
|
96
86
|
get do
|
|
@@ -120,24 +110,18 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
120
110
|
get :callback do
|
|
121
111
|
session = lookup_session!
|
|
122
112
|
code = params[:code]
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
_created, customer = oauth_provider.find_or_create_customer(tokens:, scope:)
|
|
136
|
-
membership = find_admin_membership(customer)
|
|
137
|
-
finish_org_setup(organization: membership.organization, tokens:, scope:)
|
|
138
|
-
session.update(customer:, used_at: Time.now, authorization_code: code)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
113
|
+
# Exchange the token now, in case it's invalid we don't want to find out at the end.
|
|
114
|
+
tokens = exchange_authorization_code(code)
|
|
115
|
+
session.update(token_json: tokens.as_json)
|
|
116
|
+
# Send the user to auth. We could (and did) use a "/me" endpoint here
|
|
117
|
+
# to get an email, but that pushes the trust someone is who they say they are
|
|
118
|
+
# to the oauth provider. We don't feel comfortable doing that in all cases,
|
|
119
|
+
# so we ask them to auth with WebhookDB.
|
|
120
|
+
#
|
|
121
|
+
# On top of that, many setups don't have a way to know who did the connection,
|
|
122
|
+
# nor can we be sure what org to install the replicators into,
|
|
123
|
+
# so may as well put everyone on the same path.
|
|
124
|
+
redirect "/v1/install/#{oauth_provider.key}/login?state=#{session.oauth_state}"
|
|
141
125
|
end
|
|
142
126
|
|
|
143
127
|
params do
|
|
@@ -149,6 +133,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
149
133
|
"messages/web/install-customer-login.liquid",
|
|
150
134
|
serialize_view_params: true,
|
|
151
135
|
vars: {
|
|
136
|
+
app_name: oauth_provider.app_name,
|
|
152
137
|
view: "email",
|
|
153
138
|
action_url: "/v1/install/#{oauth_provider.key}/login",
|
|
154
139
|
oauth_state: session.oauth_state,
|
|
@@ -167,23 +152,117 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
167
152
|
session = lookup_session!
|
|
168
153
|
email = params[:email]
|
|
169
154
|
raise FormError.new("Email is required", 400) unless email.present?
|
|
170
|
-
otp_token = params[:otp_token]
|
|
171
|
-
|
|
155
|
+
if (otp_token = params[:otp_token]).nil?
|
|
156
|
+
# This is the first submit, asking for email. Prompt them for an OTP.
|
|
157
|
+
handle_login(email:, session:, action_url: "/v1/install/#{oauth_provider.key}/login")
|
|
158
|
+
else
|
|
159
|
+
# This is the 'second' submit, asking for OTP.
|
|
160
|
+
# Verify it, ensure the customer has a default org, and then send them to the 'org chooser'.
|
|
172
161
|
# Order of operations here is:
|
|
173
162
|
# - Verify the OTP
|
|
174
163
|
# - Make sure we can find a valid admin membership
|
|
175
164
|
# - Only then do we exchange the token.
|
|
176
165
|
# - Setup replicators.
|
|
177
|
-
customer = find_and_verify_user(email:, otp_token:)
|
|
178
|
-
membership = find_admin_membership(customer)
|
|
179
|
-
tokens = exchange_authorization_code(session.authorization_code)
|
|
180
166
|
session.db.transaction do
|
|
181
|
-
|
|
182
|
-
|
|
167
|
+
customer = find_and_verify_user(email:, otp_token:)
|
|
168
|
+
Webhookdb::Customer.find_or_create_default_organization(customer)
|
|
169
|
+
session.update(customer:)
|
|
183
170
|
end
|
|
171
|
+
redirect "/v1/install/#{oauth_provider.key}/org?state=#{session.oauth_state}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
params do
|
|
176
|
+
requires :state, type: String
|
|
177
|
+
end
|
|
178
|
+
get :org do
|
|
179
|
+
session = lookup_session!
|
|
180
|
+
organizations = session.customer.verified_memberships.select(&:admin?).map do |m|
|
|
181
|
+
{name: m.organization.name, key: m.organization.key, checked: m.default? ? "true" : ""}
|
|
182
|
+
end
|
|
183
|
+
rendered = render_liquid(
|
|
184
|
+
"messages/web/install-org-chooser.liquid",
|
|
185
|
+
serialize_view_params: true,
|
|
186
|
+
vars: {
|
|
187
|
+
app_name: oauth_provider.app_name,
|
|
188
|
+
action_url: "/v1/install/#{oauth_provider.key}/org",
|
|
189
|
+
oauth_state: session.oauth_state,
|
|
190
|
+
organizations:,
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
status 200
|
|
194
|
+
body rendered
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
params do
|
|
198
|
+
requires :state, type: String, desc: "the user session info string that we provided to Front"
|
|
199
|
+
optional :existing_org_key, type: String
|
|
200
|
+
optional :new_org_name, type: String
|
|
201
|
+
end
|
|
202
|
+
post :org do
|
|
203
|
+
session = lookup_session!
|
|
204
|
+
if (key = params[:existing_org_key]).present?
|
|
205
|
+
membership = session.customer.verified_memberships_dataset.
|
|
206
|
+
admin.
|
|
207
|
+
where(organization: Webhookdb::Organization.where(key:)).
|
|
208
|
+
first
|
|
209
|
+
raise FormError.new("You are not an administrator of that org or it does not exist.", 400) if
|
|
210
|
+
membership.nil?
|
|
211
|
+
elsif (name = params[:new_org_name])
|
|
212
|
+
org = Webhookdb::Organization.create_if_unique(name:)
|
|
213
|
+
raise FormError.new("Sorry, an organization with that name already exists.", 400) if
|
|
214
|
+
org.nil?
|
|
215
|
+
membership = session.customer.add_membership(
|
|
216
|
+
organization: org,
|
|
217
|
+
membership_role: Webhookdb::Role.admin_role,
|
|
218
|
+
verified: true,
|
|
219
|
+
)
|
|
184
220
|
else
|
|
185
|
-
|
|
221
|
+
raise FormError.new("Existing organization key or a new organization name are required", 400)
|
|
186
222
|
end
|
|
223
|
+
|
|
224
|
+
tokens = Webhookdb::Oauth::Tokens.new(**session.token_json)
|
|
225
|
+
session.db.transaction do
|
|
226
|
+
session.customer.replace_default_membership(membership)
|
|
227
|
+
membership.organization.prepare_database_connections?
|
|
228
|
+
oauth_provider.build_marketplace_integrations(organization: membership.organization, tokens:)
|
|
229
|
+
session.update(organization: membership.organization, token_json: nil)
|
|
230
|
+
redirect "/v1/install/#{oauth_provider.key}/success?state=#{params[:state]}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
params do
|
|
235
|
+
requires :state, type: String
|
|
236
|
+
end
|
|
237
|
+
get :success do
|
|
238
|
+
session = lookup_session!
|
|
239
|
+
# Mark the session used on GET, since we use the state to look up the session.
|
|
240
|
+
# It does mean that refreshing the page will error, though.
|
|
241
|
+
session.update(used_at: Time.now)
|
|
242
|
+
rendered = render_liquid(
|
|
243
|
+
"messages/web/install-success.liquid",
|
|
244
|
+
serialize_view_params: true,
|
|
245
|
+
vars: {
|
|
246
|
+
app_name: oauth_provider.app_name,
|
|
247
|
+
database_url: session.organization.readonly_connection_url,
|
|
248
|
+
supports_webhooks: oauth_provider.supports_webhooks?,
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
status 200
|
|
252
|
+
body rendered
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
get :forbidden do
|
|
256
|
+
rendered = render_liquid(
|
|
257
|
+
"messages/web/install-forbidden.liquid",
|
|
258
|
+
vars: {
|
|
259
|
+
app_name: oauth_provider.app_name,
|
|
260
|
+
terminal_url: "#{Webhookdb.api_url}/terminal",
|
|
261
|
+
install_url: "#{Webhookdb.api_url}/v1/install/#{oauth_provider.key}",
|
|
262
|
+
},
|
|
263
|
+
)
|
|
264
|
+
status 403
|
|
265
|
+
body rendered
|
|
187
266
|
end
|
|
188
267
|
end
|
|
189
268
|
|
|
@@ -194,12 +273,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
194
273
|
whresp = Webhookdb::Front.initial_verification_request_response(request, Webhookdb::Front.app_secret)
|
|
195
274
|
s_status, s_headers, s_body = whresp.to_rack
|
|
196
275
|
s_headers.each { |k, v| header k, v }
|
|
197
|
-
|
|
198
|
-
body Oj.load(s_body)
|
|
199
|
-
else
|
|
200
|
-
env["api.format"] = :binary
|
|
201
|
-
body s_body
|
|
202
|
-
end
|
|
276
|
+
body Oj.load(s_body)
|
|
203
277
|
status s_status
|
|
204
278
|
break
|
|
205
279
|
end
|
|
@@ -258,15 +332,52 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
258
332
|
end
|
|
259
333
|
end
|
|
260
334
|
|
|
335
|
+
resource :increase do
|
|
336
|
+
params do
|
|
337
|
+
requires :id, type: String
|
|
338
|
+
requires :created_at, type: Time
|
|
339
|
+
requires :category, type: String
|
|
340
|
+
requires :associated_object_type, type: String
|
|
341
|
+
requires :associated_object_id, type: String
|
|
342
|
+
requires :type, type: String
|
|
343
|
+
end
|
|
344
|
+
post :webhook do
|
|
345
|
+
group_id = env["HTTP_INCREASE_GROUP_ID"]
|
|
346
|
+
handle_webhook_request("increase-group-#{group_id || '?'}") do
|
|
347
|
+
if group_id.nil?
|
|
348
|
+
# No group ID is one of our own events.
|
|
349
|
+
# Run the job to handle it as a platform event (usually this is the oauth disconnect)
|
|
350
|
+
Amigo.publish("increase.#{params[:category]}", declared(params).as_json)
|
|
351
|
+
status 202
|
|
352
|
+
present({message: "ok"})
|
|
353
|
+
next :pass
|
|
354
|
+
end
|
|
355
|
+
root_sint = Webhookdb::ServiceIntegration[service_name: "increase_app_v1", api_url: group_id]
|
|
356
|
+
if root_sint.nil?
|
|
357
|
+
logger.error "increase_unregistered_group", increase_group_id: group_id
|
|
358
|
+
status 202
|
|
359
|
+
present({message: "unregistered group"})
|
|
360
|
+
next :pass
|
|
361
|
+
end
|
|
362
|
+
next root_sint
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
261
367
|
resource :intercom do
|
|
368
|
+
helpers do
|
|
369
|
+
def find_root(app_id)
|
|
370
|
+
return Webhookdb::ServiceIntegration[service_name: "intercom_marketplace_root_v1", api_url: app_id]
|
|
371
|
+
end
|
|
372
|
+
end
|
|
262
373
|
post :webhook do
|
|
263
374
|
# Because the `_webhook_response` function is always the same here, I'm wondering if it's even
|
|
264
375
|
# advisable to do the integration lookup before performing a webhook verification when we don't
|
|
265
376
|
# need that info. Something to consider upon refactor
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
377
|
+
app_id = params[:app_id]
|
|
378
|
+
root_sint = find_root(app_id)
|
|
379
|
+
opaque_id = root_sint&.opaque_id || "intercom_marketplace_appid-#{app_id}"
|
|
380
|
+
handle_webhook_request(opaque_id) do
|
|
270
381
|
if root_sint.nil?
|
|
271
382
|
logger.warn "intercom_webhook_unregistered_app", intercom_app_id: app_id
|
|
272
383
|
status 200
|
|
@@ -275,6 +386,8 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
275
386
|
end
|
|
276
387
|
# Notification topics are formatted like "{model}.{thing that happened}" (e.g. "contact.created")
|
|
277
388
|
# to get the model type of the notification, for our purposes we can just grab that first chunk
|
|
389
|
+
# This should probably move to the marketplace replicator itself,
|
|
390
|
+
# rather than being done in the endpoint (see /v1/install/increase/webhook).
|
|
278
391
|
type = params[:topic].split(".")[0]
|
|
279
392
|
handling_type = "intercom_#{type}_v1"
|
|
280
393
|
unless (handling_sint = root_sint.recursive_dependents.find { |d| d.service_name == handling_type })
|
|
@@ -291,27 +404,39 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
|
291
404
|
requires :app_id
|
|
292
405
|
end
|
|
293
406
|
post :uninstall do
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
407
|
+
app_id = params[:app_id]
|
|
408
|
+
root_sint = find_root(app_id)
|
|
409
|
+
# Intercom uses X-Body-Signature rather than X-Hub-Signature here,
|
|
410
|
+
# unlike the normal /webhook request.
|
|
411
|
+
# I've asked Intercom if they can support X-Hub-Signature here as well.
|
|
412
|
+
# If they cannot, we need to add support for the alternative signature validation.
|
|
413
|
+
opaque_id = root_sint&.opaque_id || "intercom_marketplace_appid-#{app_id}"
|
|
414
|
+
handle_webhook_request(opaque_id) do
|
|
415
|
+
root_sint&.destroy_self_and_all_dependents
|
|
416
|
+
status 200
|
|
417
|
+
present({o: "k"})
|
|
418
|
+
next :pass
|
|
419
|
+
end
|
|
301
420
|
end
|
|
302
421
|
|
|
303
422
|
params do
|
|
423
|
+
# This endpoint recieves a value called "workspace_id" but it is
|
|
424
|
+
# identical to the "app_id" value we get from the `/me` endpoint.
|
|
425
|
+
# It just has a different name here for some reason.
|
|
304
426
|
requires :workspace_id
|
|
305
427
|
end
|
|
306
428
|
post :health do
|
|
307
|
-
# TODO: Verify the headers are valid
|
|
308
|
-
# For now we are just returning "OK" per the specification:
|
|
309
429
|
# https://developers.intercom.com/docs/build-an-integration/learn-more/installation-health-check
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
430
|
+
result = {}
|
|
431
|
+
if find_root(params[:workspace_id]).nil?
|
|
432
|
+
result[:state] = "UNHEALTHY"
|
|
433
|
+
result[:cta_type] = "REINSTALL_CTA"
|
|
434
|
+
result[:message] = "You need to reinstall this app to sync your data to WebhookDB."
|
|
435
|
+
else
|
|
436
|
+
result[:state] = "OK"
|
|
437
|
+
end
|
|
313
438
|
status 200
|
|
314
|
-
present
|
|
439
|
+
present result
|
|
315
440
|
end
|
|
316
441
|
end
|
|
317
442
|
end
|
|
@@ -54,7 +54,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
54
54
|
get do
|
|
55
55
|
_customer = current_customer
|
|
56
56
|
org = lookup_org!
|
|
57
|
-
fake_entities = org.
|
|
57
|
+
fake_entities = org.available_replicators.map(&:name).sort.map { |name| {name:} }
|
|
58
58
|
message = "Run `webhookdb integrations create [service name]` to start replicating data to your database."
|
|
59
59
|
present_collection fake_entities, with: Webhookdb::API::ServiceEntity, message:
|
|
60
60
|
end
|
|
@@ -62,11 +62,11 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
62
62
|
|
|
63
63
|
desc "Generates an invitation code for a user, adds pending membership in the organization."
|
|
64
64
|
params do
|
|
65
|
-
optional :email, type:
|
|
65
|
+
optional :email, type: NormalizedEmail,
|
|
66
66
|
prompt: "Enter the email to send the invitation to:"
|
|
67
67
|
optional :role_name,
|
|
68
|
-
type:
|
|
69
|
-
values: Webhookdb::OrganizationMembership::VALID_ROLE_NAMES,
|
|
68
|
+
type: TrimmedString,
|
|
69
|
+
values: TrimmedString.map(Webhookdb::OrganizationMembership::VALID_ROLE_NAMES),
|
|
70
70
|
default: "member"
|
|
71
71
|
end
|
|
72
72
|
post :invite do
|
|
@@ -102,7 +102,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
102
102
|
|
|
103
103
|
desc "Allows organization admin to remove customer from an organization"
|
|
104
104
|
params do
|
|
105
|
-
optional :email, type:
|
|
105
|
+
optional :email, type: NormalizedEmail,
|
|
106
106
|
prompt: "Enter the email of the member you are removing permissions from:"
|
|
107
107
|
optional :guard_confirm
|
|
108
108
|
end
|
|
@@ -118,7 +118,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
118
118
|
to_delete.delete
|
|
119
119
|
roll_back_if_no_admins!(org)
|
|
120
120
|
status 200
|
|
121
|
-
present({}, with: Webhookdb::
|
|
121
|
+
present({}, with: Webhookdb::API::BaseEntity,
|
|
122
122
|
message: "#{email} is no longer a part of #{org.name}.",)
|
|
123
123
|
end
|
|
124
124
|
end
|
|
@@ -149,9 +149,11 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
149
149
|
params do
|
|
150
150
|
optional :emails, type: [String], coerce_with: CommaSepArray,
|
|
151
151
|
prompt: "Enter the emails to modify the roles of as a comma-separated list:"
|
|
152
|
-
optional :role_name,
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
optional :role_name,
|
|
153
|
+
type: TrimmedString,
|
|
154
|
+
values: TrimmedString.map(Webhookdb::OrganizationMembership::VALID_ROLE_NAMES),
|
|
155
|
+
prompt: "Enter the name of the role to assign " \
|
|
156
|
+
"(#{Webhookdb::OrganizationMembership::VALID_ROLE_NAMES.join(', ')}): "
|
|
155
157
|
optional :guard_confirm
|
|
156
158
|
end
|
|
157
159
|
post :change_roles do
|
|
@@ -173,7 +175,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
173
175
|
|
|
174
176
|
desc "Allow organization admin to change the name of the organization"
|
|
175
177
|
params do
|
|
176
|
-
optional :name, type:
|
|
178
|
+
optional :name, type: TrimmedString, prompt: "Enter the new organization name:"
|
|
177
179
|
end
|
|
178
180
|
post :rename do
|
|
179
181
|
customer = current_customer
|
|
@@ -214,7 +216,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
214
216
|
|
|
215
217
|
desc "Creates a new organization and adds current customer as a member."
|
|
216
218
|
params do
|
|
217
|
-
optional :name, type:
|
|
219
|
+
optional :name, type: TrimmedString, prompt: "Enter the name of the organization:"
|
|
218
220
|
end
|
|
219
221
|
post :create do
|
|
220
222
|
customer = current_customer
|
|
@@ -234,7 +236,7 @@ class Webhookdb::API::Organizations < Webhookdb::API::V1
|
|
|
234
236
|
|
|
235
237
|
desc "Allows user to verify membership in an organization with an invitation code."
|
|
236
238
|
params do
|
|
237
|
-
optional :invitation_code, type:
|
|
239
|
+
optional :invitation_code, type: TrimmedString, prompt: "Enter the invitation code:"
|
|
238
240
|
end
|
|
239
241
|
post :join do
|
|
240
242
|
customer = current_customer
|
|
@@ -4,6 +4,8 @@ require "webhookdb/api"
|
|
|
4
4
|
require "webhookdb/saved_query"
|
|
5
5
|
|
|
6
6
|
class Webhookdb::API::SavedQueries < Webhookdb::API::V1
|
|
7
|
+
include Webhookdb::Service::Types
|
|
8
|
+
|
|
7
9
|
resource :organizations do
|
|
8
10
|
route_param :org_identifier do
|
|
9
11
|
resource :saved_queries do
|
|
@@ -99,8 +101,10 @@ class Webhookdb::API::SavedQueries < Webhookdb::API::V1
|
|
|
99
101
|
|
|
100
102
|
desc "Updates the field on a custom query."
|
|
101
103
|
params do
|
|
102
|
-
optional :field,
|
|
103
|
-
|
|
104
|
+
optional :field,
|
|
105
|
+
type: TrimmedString,
|
|
106
|
+
prompt: "What field would you like to update (one of: " \
|
|
107
|
+
"#{Webhookdb::SavedQuery::CLI_EDITABLE_FIELDS.join(', ')}): "
|
|
104
108
|
optional :value, type: String, prompt: "What is the new value? "
|
|
105
109
|
end
|
|
106
110
|
post :update do
|
|
@@ -147,7 +151,9 @@ class Webhookdb::API::SavedQueries < Webhookdb::API::V1
|
|
|
147
151
|
end
|
|
148
152
|
|
|
149
153
|
params do
|
|
150
|
-
optional :field,
|
|
154
|
+
optional :field,
|
|
155
|
+
type: TrimmedString,
|
|
156
|
+
values: TrimmedString.map(Webhookdb::SavedQuery::INFO_FIELDS.keys + [""])
|
|
151
157
|
end
|
|
152
158
|
post :info do
|
|
153
159
|
cq = lookup!
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webhookdb/api"
|
|
4
|
+
|
|
5
|
+
class Webhookdb::API::SavedViews < Webhookdb::API::V1
|
|
6
|
+
include Webhookdb::Service::Types
|
|
7
|
+
|
|
8
|
+
resource :organizations do
|
|
9
|
+
route_param :org_identifier do
|
|
10
|
+
resource :saved_views do
|
|
11
|
+
helpers do
|
|
12
|
+
def lookup_view!
|
|
13
|
+
org = lookup_org!
|
|
14
|
+
cq = org.saved_views_dataset[name: params[:name].strip]
|
|
15
|
+
merror!(403, "There is no view with that name.") if cq.nil?
|
|
16
|
+
return cq
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def guard_editable!(customer, org)
|
|
20
|
+
return if has_admin?(org, customer:)
|
|
21
|
+
permission_error!("You must be an org admin to modify views.")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Returns a list of all saved views associated with the org."
|
|
26
|
+
get do
|
|
27
|
+
views = lookup_org!.saved_views
|
|
28
|
+
message = ""
|
|
29
|
+
if views.empty?
|
|
30
|
+
message = "This organization doesn't have any saved views yet.\n" \
|
|
31
|
+
"Use `webhookdb saved-view create` to set one up."
|
|
32
|
+
end
|
|
33
|
+
present_collection views, with: SavedViewEntity, message:
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Creates or replaces the view with the given name."
|
|
37
|
+
params do
|
|
38
|
+
optional :name,
|
|
39
|
+
type: TrimmedString,
|
|
40
|
+
prompt: "Enter the view name (alphanumeric, spaces, underscores):"
|
|
41
|
+
optional :sql, type: String, prompt: "Enter the SQL you would like to run:"
|
|
42
|
+
end
|
|
43
|
+
post :create_or_replace do
|
|
44
|
+
cust = current_customer
|
|
45
|
+
org = lookup_org!
|
|
46
|
+
check_feature_access!(org, Webhookdb::SavedView.feature_role)
|
|
47
|
+
guard_editable!(cust, org)
|
|
48
|
+
begin
|
|
49
|
+
sv = Webhookdb::SavedView.create_or_replace(
|
|
50
|
+
organization: org,
|
|
51
|
+
sql: params[:sql],
|
|
52
|
+
name: params[:name].strip,
|
|
53
|
+
created_by: cust,
|
|
54
|
+
)
|
|
55
|
+
rescue Webhookdb::SavedView::InvalidQuery => e
|
|
56
|
+
Webhookdb::API::Helpers.prompt_for_required_param!(
|
|
57
|
+
request,
|
|
58
|
+
:sql,
|
|
59
|
+
"Enter a new query:",
|
|
60
|
+
output: "That query was invalid. #{e.message}\n" \
|
|
61
|
+
"You can iterate on your query by connecting to your database from any SQL editor.\n" \
|
|
62
|
+
"Use `webhookdb db connection` to get your query string.",
|
|
63
|
+
)
|
|
64
|
+
rescue Webhookdb::DBAdapter::InvalidIdentifier => e
|
|
65
|
+
Webhookdb::API::Helpers.prompt_for_required_param!(
|
|
66
|
+
request,
|
|
67
|
+
:name,
|
|
68
|
+
"Enter a new name:",
|
|
69
|
+
output: e.message,
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
message = "You have created or replaced the view with the name '#{sv.name}'. " \
|
|
73
|
+
"You can now use it in any query with your database connection string. " \
|
|
74
|
+
"Run `webhookdb db connection` to retrieve your connection string if you need it."
|
|
75
|
+
status 200
|
|
76
|
+
present sv, with: SavedViewEntity, message:
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
post :delete do
|
|
80
|
+
customer = current_customer
|
|
81
|
+
cq = lookup_view!
|
|
82
|
+
guard_editable!(customer, cq.organization)
|
|
83
|
+
cq.destroy
|
|
84
|
+
status 200
|
|
85
|
+
present cq, with: SavedViewEntity,
|
|
86
|
+
message: "You have successfully deleted the saved view '#{cq.name}'."
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class SavedViewEntity < Webhookdb::API::BaseEntity
|
|
93
|
+
expose :name
|
|
94
|
+
|
|
95
|
+
def self.display_headers
|
|
96
|
+
return [[:name, "Name"]]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|