vortex-ruby-sdk 1.8.4 → 1.15.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 677efb240644fe95c61d07a325952a6d2f8c6851eb14d59d17178d2c74881629
4
- data.tar.gz: 83ad91fa108419346c2b6a707b62316573fdc5137d213a141840a599eca02dbe
3
+ metadata.gz: 2fa76d2aa711c84370eb93e563946ee45c78c12d2a5c54d6b4de13ebb49f3c6a
4
+ data.tar.gz: 74e291d07ca213da127b5cb35e8964e63664d101ffff8fe06aa3e586b8efc42f
5
5
  SHA512:
6
- metadata.gz: 324e85fc0660f7b952932a163ad10df4ae17f8f81dafd7c8eb6d8de70e771146474c0cc147d7643ae689f9629eafba0b9884c6f022b3091fb4147a3a1dc2f1eb
7
- data.tar.gz: 2d780b17c5697ffcb0532bfcabce7756bd80a672b74417b5194e0c3e9089de31c0bc8c03977b4a0fdce0e416af1bb2a85868b2aa6050d2bf6a9fe9fbf7aa45e5
6
+ metadata.gz: ef4b13b91543a141f041d2bfcc812b48e55a63998a7d8da7e445107daec73bb10e3d047532dfce015ed51a8160ddf5df28f78ca46285311fefa3a6f692ad40af
7
+ data.tar.gz: 9a8723995e0cdfae388688385544242e30c2c280192938ac59aebfa724f5ebf0f3508ea1eb44765c92d8ec936885bdef30ee0b828fab6ecc4c4746b29a19860f
@@ -5,10 +5,12 @@
5
5
  **Requires:** Ruby 3.0+
6
6
 
7
7
  ## Prerequisites
8
+
8
9
  From integration contract you need: API endpoint prefix, scope entity, authentication pattern
9
10
  From discovery data you need: Ruby framework (Rails, Sinatra), database ORM, auth pattern
10
11
 
11
12
  ## Key Facts
13
+
12
14
  - Framework-agnostic Ruby SDK
13
15
  - Client-based: instantiate `Vortex::Client` class and call methods
14
16
  - Built-in Rails and Sinatra helpers
@@ -20,11 +22,13 @@ From discovery data you need: Ruby framework (Rails, Sinatra), database ORM, aut
20
22
  ## Step 1: Install
21
23
 
22
24
  Add to `Gemfile`:
25
+
23
26
  ```ruby
24
27
  gem 'vortex-ruby-sdk'
25
28
  ```
26
29
 
27
30
  Then install:
31
+
28
32
  ```bash
29
33
  bundle install
30
34
  ```
@@ -49,6 +53,7 @@ VORTEX_API_KEY=VRTX.your-api-key-here.secret
49
53
  ## Step 3: Create Vortex Client
50
54
 
51
55
  ### Rails Initializer (`config/initializers/vortex.rb`):
56
+
52
57
  ```ruby
53
58
  Rails.application.config.vortex = Vortex::Client.new(
54
59
  Rails.application.credentials.vortex_api_key || ENV['VORTEX_API_KEY']
@@ -56,6 +61,7 @@ Rails.application.config.vortex = Vortex::Client.new(
56
61
  ```
57
62
 
58
63
  ### Rails Concern (`app/controllers/concerns/vortex_helper.rb`):
64
+
59
65
  ```ruby
60
66
  module VortexHelper
61
67
  extend ActiveSupport::Concern
@@ -71,6 +77,7 @@ end
71
77
  ```
72
78
 
73
79
  ### Sinatra Configuration:
80
+
74
81
  ```ruby
75
82
  # app.rb or config.ru
76
83
  require 'sinatra/base'
@@ -94,6 +101,7 @@ end
94
101
  ## Step 4: Extract Authenticated User
95
102
 
96
103
  ### Rails with Devise:
104
+
97
105
  ```ruby
98
106
  # app/controllers/application_controller.rb
99
107
  class ApplicationController < ActionController::API
@@ -116,6 +124,7 @@ end
116
124
  ```
117
125
 
118
126
  ### Rails with JWT:
127
+
119
128
  ```ruby
120
129
  class ApplicationController < ActionController::API
121
130
  before_action :authenticate_user_from_token!
@@ -147,6 +156,7 @@ end
147
156
  ```
148
157
 
149
158
  ### Sinatra:
159
+
150
160
  ```ruby
151
161
  # app/helpers/auth_helper.rb
152
162
  module AuthHelper
@@ -185,6 +195,7 @@ end
185
195
  ```
186
196
 
187
197
  **Adapt to their patterns:**
198
+
188
199
  - Match their auth mechanism (Devise, JWT, sessions)
189
200
  - Match their user structure
190
201
  - Match their admin detection logic
@@ -194,6 +205,7 @@ end
194
205
  ## Step 5: Implement JWT Generation Endpoint
195
206
 
196
207
  ### Rails (`app/controllers/vortex_controller.rb`):
208
+
197
209
  ```ruby
198
210
  class VortexController < ApplicationController
199
211
  before_action :require_authentication!
@@ -217,6 +229,7 @@ end
217
229
  ```
218
230
 
219
231
  ### Sinatra:
232
+
220
233
  ```ruby
221
234
  require 'sinatra/base'
222
235
  require 'json'
@@ -256,6 +269,7 @@ end
256
269
  ## Step 6: Implement Accept Invitations Endpoint (CRITICAL)
257
270
 
258
271
  ### Rails Routes (`config/routes.rb`):
272
+
259
273
  ```ruby
260
274
  Rails.application.routes.draw do
261
275
  namespace :api do
@@ -272,6 +286,7 @@ end
272
286
  ```
273
287
 
274
288
  ### Rails with ActiveRecord:
289
+
275
290
  ```ruby
276
291
  class Api::VortexController < ApplicationController
277
292
  before_action :require_authentication!
@@ -292,11 +307,11 @@ class Api::VortexController < ApplicationController
292
307
  invitation_ids.each do |invitation_id|
293
308
  invitation = vortex_client.get_invitation(invitation_id)
294
309
 
295
- (invitation['groups'] || []).each do |group|
296
- GroupMembership.find_or_create_by!(
310
+ (invitation["groups"] || []).each do |scope|
311
+ ScopeMembership.find_or_create_by!(
297
312
  user_id: current_user.id,
298
- group_type: group['type'],
299
- group_id: group['groupId']
313
+ scope_type: scope['type'],
314
+ scope_id: scope['groupId']
300
315
  ) do |membership|
301
316
  membership.role = invitation['role'] || 'member'
302
317
  end
@@ -320,6 +335,7 @@ end
320
335
  ```
321
336
 
322
337
  ### Sinatra with Sequel:
338
+
323
339
  ```ruby
324
340
  post '/api/vortex/invitations/accept' do
325
341
  require_authentication!
@@ -340,14 +356,14 @@ post '/api/vortex/invitations/accept' do
340
356
  invitation_ids.each do |invitation_id|
341
357
  invitation = vortex.get_invitation(invitation_id)
342
358
 
343
- (invitation['groups'] || []).each do |group|
344
- GroupMembership.insert_conflict(
345
- target: [:user_id, :group_type, :group_id],
359
+ (invitation["groups"] || []).each do |scope|
360
+ ScopeMembership.insert_conflict(
361
+ target: [:user_id, :scope_type, :scope_id],
346
362
  update: { role: invitation['role'] || 'member' }
347
363
  ).insert(
348
364
  user_id: current_user.id,
349
- group_type: group['type'],
350
- group_id: group['groupId'],
365
+ scope_type: scope['type'],
366
+ scope_id: scope['groupId'],
351
367
  role: invitation['role'] || 'member',
352
368
  joined_at: Time.now
353
369
  )
@@ -368,6 +384,7 @@ end
368
384
  ```
369
385
 
370
386
  **Critical - Adapt database logic:**
387
+
371
388
  - Use their actual table/model names (from discovery)
372
389
  - Use their actual field names
373
390
  - Use their ORM pattern (ActiveRecord, Sequel)
@@ -378,19 +395,20 @@ end
378
395
  ## Step 7: Database Models
379
396
 
380
397
  ### Rails Migration:
398
+
381
399
  ```ruby
382
- # db/migrate/YYYYMMDDHHMMSS_create_group_memberships.rb
383
- class CreateGroupMemberships < ActiveRecord::Migration[7.0]
400
+ # db/migrate/YYYYMMDDHHMMSS_create_scope_memberships.rb
401
+ class CreateScopeMemberships < ActiveRecord::Migration[7.0]
384
402
  def change
385
- create_table :group_memberships do |t|
403
+ create_table :scope_memberships do |t|
386
404
  t.string :user_id, null: false
387
- t.string :group_type, null: false, limit: 100
388
- t.string :group_id, null: false
405
+ t.string :scope_type, null: false, limit: 100
406
+ t.string :scope_id, null: false
389
407
  t.string :role, default: 'member', limit: 50
390
408
  t.timestamp :joined_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
391
409
 
392
- t.index [:user_id, :group_type, :group_id], unique: true, name: 'unique_membership'
393
- t.index [:group_type, :group_id], name: 'idx_group'
410
+ t.index [:user_id, :scope_type, :scope_id], unique: true, name: 'unique_membership'
411
+ t.index [:scope_type, :scope_id], name: 'idx_scope'
394
412
  t.index [:user_id], name: 'idx_user'
395
413
  end
396
414
  end
@@ -398,33 +416,35 @@ end
398
416
  ```
399
417
 
400
418
  ### Rails Model:
419
+
401
420
  ```ruby
402
- # app/models/group_membership.rb
403
- class GroupMembership < ApplicationRecord
421
+ # app/models/scope_membership.rb
422
+ class ScopeMembership < ApplicationRecord
404
423
  validates :user_id, presence: true
405
- validates :group_type, presence: true
406
- validates :group_id, presence: true
424
+ validates :scope_type, presence: true
425
+ validates :scope_id, presence: true
407
426
  validates :role, presence: true
408
427
 
409
- validates :user_id, uniqueness: { scope: [:group_type, :group_id] }
428
+ validates :user_id, uniqueness: { scope: [:scope_type, :scope_id] }
410
429
  end
411
430
  ```
412
431
 
413
432
  ### Sequel Migration:
433
+
414
434
  ```ruby
415
- # db/migrations/001_create_group_memberships.rb
435
+ # db/migrations/001_create_scope_memberships.rb
416
436
  Sequel.migration do
417
437
  change do
418
- create_table(:group_memberships) do
438
+ create_table(:scope_memberships) do
419
439
  primary_key :id
420
440
  String :user_id, null: false
421
- String :group_type, size: 100, null: false
422
- String :group_id, null: false
441
+ String :scope_type, size: 100, null: false
442
+ String :scope_id, null: false
423
443
  String :role, size: 50, default: 'member'
424
444
  DateTime :joined_at, null: false, default: Sequel::CURRENT_TIMESTAMP
425
445
 
426
- index [:user_id, :group_type, :group_id], unique: true, name: :unique_membership
427
- index [:group_type, :group_id], name: :idx_group
446
+ index [:user_id, :scope_type, :scope_id], unique: true, name: :unique_membership
447
+ index [:scope_type, :scope_id], name: :idx_scope
428
448
  index [:user_id], name: :idx_user
429
449
  end
430
450
  end
@@ -450,6 +470,7 @@ curl -X POST http://localhost:3000/api/vortex/jwt \
450
470
  ```
451
471
 
452
472
  Expected response:
473
+
453
474
  ```json
454
475
  {
455
476
  "jwt": "eyJhbGciOiJIUzI1NiIs..."
@@ -471,6 +492,7 @@ Expected response:
471
492
  **CORS errors** → Add CORS middleware:
472
493
 
473
494
  **Rails:**
495
+
474
496
  ```ruby
475
497
  # Gemfile
476
498
  gem 'rack-cors'
@@ -485,6 +507,7 @@ end
485
507
  ```
486
508
 
487
509
  **Sinatra:**
510
+
488
511
  ```ruby
489
512
  require 'sinatra/cross_origin'
490
513
 
@@ -508,13 +531,15 @@ end
508
531
  ## After Implementation Report
509
532
 
510
533
  List files created/modified:
534
+
511
535
  - Dependency: Gemfile
512
536
  - Client: config/initializers/vortex.rb (or concern)
513
537
  - Controller: app/controllers/vortex_controller.rb (or Sinatra routes)
514
- - Model: app/models/group_membership.rb
515
- - Migration: db/migrate/XXX_create_group_memberships.rb
538
+ - Model: app/models/scope_membership.rb
539
+ - Migration: db/migrate/XXX_create_scope_memberships.rb
516
540
 
517
541
  Confirm:
542
+
518
543
  - Vortex gem installed
519
544
  - VortexClient instance created
520
545
  - JWT endpoint returns valid JWT
@@ -525,6 +550,7 @@ Confirm:
525
550
  ## Endpoints Registered
526
551
 
527
552
  All endpoints at `/api/vortex`:
553
+
528
554
  - `POST /jwt` - Generate JWT for authenticated user
529
555
  - `GET /invitations` - Get invitations by target
530
556
  - `GET /invitations/:id` - Get invitation by ID
data/README.md CHANGED
@@ -61,8 +61,37 @@ invitations = client.get_invitations_by_target('email', 'user@example.com')
61
61
  # Accept an invitation
62
62
  client.accept_invitation('inv-123', { email: 'user@example.com' })
63
63
 
64
- # Get invitations by group
65
- group_invitations = client.get_invitations_by_group('team', 'team1')
64
+ # Accept with new vs existing user tracking
65
+ # is_existing: true if user was already registered, false if new signup
66
+ client.accept_invitation('inv-123', {
67
+ email: 'user@example.com',
68
+ is_existing: false # New user signup
69
+ })
70
+
71
+ # Get invitations by scope
72
+ scope_invitations = client.get_invitations_by_scope('team', 'team1')
73
+ ```
74
+
75
+ ## Token Generation (for Widgets)
76
+
77
+ Use `generate_token` for widget authentication. This method generates a signed JWT token that can be passed to Vortex widgets via the `token` prop.
78
+
79
+ ```ruby
80
+ # Sign just the user (minimum for secure invitation attribution)
81
+ token = client.generate_token({ user: { id: 'user-123' } })
82
+
83
+ # Sign full payload with component, scope, and variables
84
+ token = client.generate_token({
85
+ component: 'widget-abc',
86
+ user: { id: 'user-123', name: 'Peter', email: 'peter@example.com' },
87
+ scope: 'workspace_456',
88
+ vars: { company_name: 'Acme' }
89
+ })
90
+
91
+ # Custom expiration (default is 5 minutes)
92
+ payload = { user: { id: 'user-123' } }
93
+ token = client.generate_token(payload, { expires_in: '1h' }) # Supports "5m", "1h", "24h", "7d"
94
+ token = client.generate_token(payload, { expires_in: 3600 }) # Or seconds as integer
66
95
  ```
67
96
 
68
97
  ## Rails Integration
@@ -116,8 +145,8 @@ Rails.application.routes.draw do
116
145
  get 'invitations/:invitation_id', action: 'get_invitation'
117
146
  delete 'invitations/:invitation_id', action: 'revoke_invitation'
118
147
  post 'invitations/accept', action: 'accept_invitations'
119
- get 'invitations/by-group/:group_type/:group_id', action: 'get_invitations_by_group'
120
- delete 'invitations/by-group/:group_type/:group_id', action: 'delete_invitations_by_group'
148
+ get 'invitations/by-scope/:scope_type/:scope', action: 'get_invitations_by_scope'
149
+ delete 'invitations/by-scope/:scope_type/:scope', action: 'delete_invitations_by_scope'
121
150
  post 'invitations/:invitation_id/reinvite', action: 'reinvite'
122
151
  end
123
152
  end
@@ -171,8 +200,8 @@ All methods match the functionality of other Vortex SDKs:
171
200
  - `get_invitation(invitation_id)` - Get specific invitation
172
201
  - `revoke_invitation(invitation_id)` - Revoke invitation
173
202
  - `accept_invitation(invitation_id, user)` - Accept an invitation
174
- - `get_invitations_by_group(group_type, group_id)` - Get group invitations
175
- - `delete_invitations_by_group(group_type, group_id)` - Delete group invitations
203
+ - `get_invitations_by_scope(scope_type, scope)` - Get scope invitations
204
+ - `delete_invitations_by_scope(scope_type, scope)` - Delete scope invitations
176
205
  - `reinvite(invitation_id)` - Reinvite user
177
206
  - `sync_internal_invitation(creator_id, target_value, action, component_id)` - Sync internal invitation action
178
207
 
@@ -182,12 +211,12 @@ The SDK provides these routes (same as other SDKs for React provider compatibili
182
211
 
183
212
  - `POST /api/vortex/jwt`
184
213
  - `GET /api/vortex/invitations?targetType=email&targetValue=user@example.com`
185
- - `GET /api/vortex/invitations/:id`
186
- - `DELETE /api/vortex/invitations/:id`
214
+ - `GET /api/vortex/invitations/:invitation_id`
215
+ - `DELETE /api/vortex/invitations/:invitation_id`
187
216
  - `POST /api/vortex/invitations/accept`
188
- - `GET /api/vortex/invitations/by-group/:type/:id`
189
- - `DELETE /api/vortex/invitations/by-group/:type/:id`
190
- - `POST /api/vortex/invitations/:id/reinvite`
217
+ - `GET /api/vortex/invitations/by-scope/:scope_type/:scope`
218
+ - `DELETE /api/vortex/invitations/by-scope/:scope_type/:scope`
219
+ - `POST /api/vortex/invitations/:invitation_id/reinvite`
191
220
  - `POST /api/vortex/invitations/sync-internal-invitation`
192
221
 
193
222
  ## Sync Internal Invitation
@@ -208,16 +237,19 @@ puts "Invitation IDs: #{result['invitationIds']}"
208
237
  ```
209
238
 
210
239
  **Parameters:**
240
+
211
241
  - `creator_id` (String) — The inviter's user ID in your system
212
242
  - `target_value` (String) — The invitee's user ID in your system
213
243
  - `action` ("accepted" | "declined") — The invitation decision
214
244
  - `component_id` (String) — The widget component UUID
215
245
 
216
246
  **Response:**
247
+
217
248
  - `processed` (Integer) — Count of invitations processed
218
249
  - `invitationIds` (Array<String>) — IDs of processed invitations
219
250
 
220
251
  **Use cases:**
252
+
221
253
  - You handle invitation delivery through your own in-app notifications or UI
222
254
  - Users accept/decline invitations within your application
223
255
  - You need to keep Vortex updated with the invitation status
@@ -262,6 +294,50 @@ To install this gem onto your local machine, run `bundle exec rake install`.
262
294
 
263
295
  ## Contributing
264
296
 
297
+ ## Webhooks
298
+
299
+ The SDK provides built-in support for verifying and parsing incoming webhook events from Vortex.
300
+
301
+ ```ruby
302
+ require 'vortex'
303
+
304
+ webhooks = Vortex::Webhooks.new(secret: ENV['VORTEX_WEBHOOK_SECRET'])
305
+
306
+ # In your HTTP handler (Rails example):
307
+ class WebhooksController < ApplicationController
308
+ skip_before_action :verify_authenticity_token
309
+
310
+ def create
311
+ payload = request.body.read
312
+ signature = request.headers['X-Vortex-Signature']
313
+
314
+ begin
315
+ event = webhooks.construct_event(payload, signature)
316
+ rescue Vortex::WebhookSignatureError
317
+ head :bad_request
318
+ return
319
+ end
320
+
321
+ case event
322
+ when Vortex::WebhookEvent
323
+ Rails.logger.info "Webhook event: #{event.type}"
324
+ when Vortex::AnalyticsEvent
325
+ Rails.logger.info "Analytics event: #{event.name}"
326
+ end
327
+
328
+ head :ok
329
+ end
330
+ end
331
+ ```
332
+
333
+ ### Event Type Constants
334
+
335
+ ```ruby
336
+ if event.type == Vortex::WebhookEventTypes::INVITATION_ACCEPTED
337
+ # Handle invitation accepted
338
+ end
339
+ ```
340
+
265
341
  Bug reports and pull requests are welcome on GitHub at https://github.com/vortexsoftware/vortex-ruby-sdk.
266
342
 
267
343
  ## License
@@ -50,7 +50,7 @@ begin
50
50
 
51
51
  # 3. Get invitations by group
52
52
  puts "3. Getting invitations for team group..."
53
- group_invitations = client.get_invitations_by_group('team', 'team1')
53
+ group_invitations = client.get_invitations_by_scope('team', 'team1')
54
54
  puts "Found #{group_invitations.length} group invitation(s)"
55
55
  puts
56
56
 
@@ -79,8 +79,8 @@ Rails.application.routes.draw do
79
79
  get 'invitations/:invitation_id', action: 'get_invitation'
80
80
  delete 'invitations/:invitation_id', action: 'revoke_invitation'
81
81
  post 'invitations/accept', action: 'accept_invitations'
82
- get 'invitations/by-group/:group_type/:group_id', action: 'get_invitations_by_group'
83
- delete 'invitations/by-group/:group_type/:group_id', action: 'delete_invitations_by_group'
82
+ get 'invitations/by-scope/:scope_type/:scope', action: 'get_invitations_by_scope'
83
+ delete 'invitations/by-scope/:scope_type/:scope', action: 'delete_invitations_by_scope'
84
84
  post 'invitations/:invitation_id/reinvite', action: 'reinvite'
85
85
  end
86
86
 
@@ -99,8 +99,8 @@ Rails.application.routes.draw do
99
99
  invitation: 'GET /api/vortex/invitations/:id',
100
100
  revoke: 'DELETE /api/vortex/invitations/:id',
101
101
  accept: 'POST /api/vortex/invitations/accept',
102
- group_invitations: 'GET /api/vortex/invitations/by-group/:type/:id',
103
- delete_group: 'DELETE /api/vortex/invitations/by-group/:type/:id',
102
+ group_invitations: 'GET /api/vortex/invitations/by-scope/:type/:id',
103
+ delete_group: 'DELETE /api/vortex/invitations/by-scope/:type/:id',
104
104
  reinvite: 'POST /api/vortex/invitations/:id/reinvite'
105
105
  }
106
106
  }.to_json
@@ -119,8 +119,8 @@ if __FILE__ == $0
119
119
  puts " GET /api/vortex/invitations/:id"
120
120
  puts " DELETE /api/vortex/invitations/:id"
121
121
  puts " POST /api/vortex/invitations/accept"
122
- puts " GET /api/vortex/invitations/by-group/:type/:id"
123
- puts " DELETE /api/vortex/invitations/by-group/:type/:id"
122
+ puts " GET /api/vortex/invitations/by-scope/:type/:id"
123
+ puts " DELETE /api/vortex/invitations/by-scope/:type/:id"
124
124
  puts " POST /api/vortex/invitations/:id/reinvite"
125
125
 
126
126
  Rails.application.initialize!
@@ -73,8 +73,8 @@ class VortexSinatraApp < Sinatra::Base
73
73
  invitation: 'GET /api/vortex/invitations/:id',
74
74
  revoke: 'DELETE /api/vortex/invitations/:id',
75
75
  accept: 'POST /api/vortex/invitations/accept',
76
- group_invitations: 'GET /api/vortex/invitations/by-group/:type/:id',
77
- delete_group: 'DELETE /api/vortex/invitations/by-group/:type/:id',
76
+ group_invitations: 'GET /api/vortex/invitations/by-scope/:type/:id',
77
+ delete_group: 'DELETE /api/vortex/invitations/by-scope/:type/:id',
78
78
  reinvite: 'POST /api/vortex/invitations/:id/reinvite'
79
79
  }
80
80
  }.to_json
@@ -105,8 +105,8 @@ if __FILE__ == $0
105
105
  puts " GET /api/vortex/invitations/:id"
106
106
  puts " DELETE /api/vortex/invitations/:id"
107
107
  puts " POST /api/vortex/invitations/accept"
108
- puts " GET /api/vortex/invitations/by-group/:type/:id"
109
- puts " DELETE /api/vortex/invitations/by-group/:type/:id"
108
+ puts " GET /api/vortex/invitations/by-scope/:type/:id"
109
+ puts " DELETE /api/vortex/invitations/by-scope/:type/:id"
110
110
  puts " POST /api/vortex/invitations/:id/reinvite"
111
111
  puts
112
112
  puts "Authentication headers (for testing):"