vortex-ruby-sdk 1.1.0 → 1.1.3

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: e7f6c2f423b5aa8099f7ccce759daaf6dcdd114c6052f1ffb9d9f5072d505916
4
- data.tar.gz: 46755e0df9a92907219042a2f2373f6f067aa46f57a1409159cc215ae14823f3
3
+ metadata.gz: c2f54f415260bef01b6106dc5d32cf274e4b9c45e00cdd89e18ad6dfbc1df177
4
+ data.tar.gz: 32e1ab2a8a7bc1ee1a3a683d82832a301f4e3403289a64ff8eea4c77410dc104
5
5
  SHA512:
6
- metadata.gz: 55c8a0671bcad40614d75363ef1b0aaf7b2323f47e9dd717940f59fd026b449c64d79b6c7b9bbed6493199c4c3d1beaac97c3e728409407339bb41363c93b1f9
7
- data.tar.gz: fcce54f801ef8115f7567cded83704409fb8436f34056aa296c77210099e0519dd59d8b51f29ff8eb05669acefb088e5cdf4a8a7f2603033f84de1767192b025
6
+ metadata.gz: 61262106b17af91ce480f7fac66a79a9d4bf2b7b77c264eba92ea07bf7a82967991e3629b59c84f0edbed77054a4f3f9ac661f9a101a4e28f8dd1efc16deafe9
7
+ data.tar.gz: efc03cda77c4d08a66ef056da5d6c72930ce0003bd6418e2cb145a6a95930e0895f7057d4e864c6c78ca7c5a69ac05dca35d0d6e17ea1aec01e480d795244a32
@@ -0,0 +1,533 @@
1
+ # Vortex Ruby SDK Implementation Guide
2
+
3
+ **Gem:** `vortex-ruby-sdk`
4
+ **Type:** Base SDK (Core library for Ruby applications)
5
+ **Requires:** Ruby 3.0+
6
+
7
+ ## Prerequisites
8
+ From integration contract you need: API endpoint prefix, scope entity, authentication pattern
9
+ From discovery data you need: Ruby framework (Rails, Sinatra), database ORM, auth pattern
10
+
11
+ ## Key Facts
12
+ - Framework-agnostic Ruby SDK
13
+ - Client-based: instantiate `Vortex::Client` class and call methods
14
+ - Built-in Rails and Sinatra helpers
15
+ - Faraday-based HTTP client
16
+ - Accept invitations requires custom database logic (must implement)
17
+
18
+ ---
19
+
20
+ ## Step 1: Install
21
+
22
+ Add to `Gemfile`:
23
+ ```ruby
24
+ gem 'vortex-ruby-sdk'
25
+ ```
26
+
27
+ Then install:
28
+ ```bash
29
+ bundle install
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Step 2: Set Environment Variable
35
+
36
+ Add to `.env`:
37
+
38
+ ```bash
39
+ VORTEX_API_KEY=VRTX.your-api-key-here.secret
40
+ ```
41
+
42
+ **Rails:** Use `config/credentials.yml.enc` or `dotenv-rails` gem
43
+ **Sinatra:** Use `dotenv` gem
44
+
45
+ **Never commit API key to version control.**
46
+
47
+ ---
48
+
49
+ ## Step 3: Create Vortex Client
50
+
51
+ ### Rails Initializer (`config/initializers/vortex.rb`):
52
+ ```ruby
53
+ Rails.application.config.vortex = Vortex::Client.new(
54
+ Rails.application.credentials.vortex_api_key || ENV['VORTEX_API_KEY']
55
+ )
56
+ ```
57
+
58
+ ### Rails Concern (`app/controllers/concerns/vortex_helper.rb`):
59
+ ```ruby
60
+ module VortexHelper
61
+ extend ActiveSupport::Concern
62
+
63
+ private
64
+
65
+ def vortex_client
66
+ @vortex_client ||= Vortex::Client.new(
67
+ Rails.application.credentials.vortex_api_key || ENV['VORTEX_API_KEY']
68
+ )
69
+ end
70
+ end
71
+ ```
72
+
73
+ ### Sinatra Configuration:
74
+ ```ruby
75
+ # app.rb or config.ru
76
+ require 'sinatra/base'
77
+ require 'vortex'
78
+
79
+ class MyApp < Sinatra::Base
80
+ configure do
81
+ set :vortex_client, Vortex::Client.new(ENV['VORTEX_API_KEY'])
82
+ end
83
+
84
+ helpers do
85
+ def vortex
86
+ settings.vortex_client
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Step 4: Extract Authenticated User
95
+
96
+ ### Rails with Devise:
97
+ ```ruby
98
+ # app/controllers/application_controller.rb
99
+ class ApplicationController < ActionController::API
100
+ private
101
+
102
+ def current_vortex_user
103
+ return nil unless current_user
104
+
105
+ {
106
+ id: current_user.id.to_s,
107
+ email: current_user.email,
108
+ admin_scopes: current_user.admin? ? ['autojoin'] : []
109
+ }
110
+ end
111
+
112
+ def require_authentication!
113
+ render json: { error: 'Unauthorized' }, status: :unauthorized unless current_user
114
+ end
115
+ end
116
+ ```
117
+
118
+ ### Rails with JWT:
119
+ ```ruby
120
+ class ApplicationController < ActionController::API
121
+ before_action :authenticate_user_from_token!
122
+
123
+ private
124
+
125
+ def authenticate_user_from_token!
126
+ token = request.headers['Authorization']&.split(' ')&.last
127
+ return render json: { error: 'Unauthorized' }, status: :unauthorized unless token
128
+
129
+ begin
130
+ payload = JWT.decode(token, Rails.application.credentials.secret_key_base, true, algorithm: 'HS256')[0]
131
+ @current_user = User.find(payload['user_id'])
132
+ rescue JWT::DecodeError, ActiveRecord::RecordNotFound
133
+ render json: { error: 'Unauthorized' }, status: :unauthorized
134
+ end
135
+ end
136
+
137
+ def current_vortex_user
138
+ return nil unless @current_user
139
+
140
+ {
141
+ id: @current_user.id.to_s,
142
+ email: @current_user.email,
143
+ admin_scopes: @current_user.admin? ? ['autojoin'] : []
144
+ }
145
+ end
146
+ end
147
+ ```
148
+
149
+ ### Sinatra:
150
+ ```ruby
151
+ # app/helpers/auth_helper.rb
152
+ module AuthHelper
153
+ def current_user
154
+ return @current_user if defined?(@current_user)
155
+
156
+ # Session-based
157
+ if session[:user_id]
158
+ @current_user = User.find(session[:user_id])
159
+ # JWT-based
160
+ elsif request.env['HTTP_AUTHORIZATION']
161
+ token = request.env['HTTP_AUTHORIZATION'].split(' ').last
162
+ payload = JWT.decode(token, ENV['SECRET_KEY_BASE'], true, algorithm: 'HS256')[0]
163
+ @current_user = User.find(payload['user_id'])
164
+ end
165
+
166
+ @current_user
167
+ rescue
168
+ nil
169
+ end
170
+
171
+ def current_vortex_user
172
+ return nil unless current_user
173
+
174
+ {
175
+ id: current_user.id.to_s,
176
+ email: current_user.email,
177
+ admin_scopes: current_user.admin? ? ['autojoin'] : []
178
+ }
179
+ end
180
+
181
+ def require_authentication!
182
+ halt 401, { error: 'Unauthorized' }.to_json unless current_user
183
+ end
184
+ end
185
+ ```
186
+
187
+ **Adapt to their patterns:**
188
+ - Match their auth mechanism (Devise, JWT, sessions)
189
+ - Match their user structure
190
+ - Match their admin detection logic
191
+
192
+ ---
193
+
194
+ ## Step 5: Implement JWT Generation Endpoint
195
+
196
+ ### Rails (`app/controllers/vortex_controller.rb`):
197
+ ```ruby
198
+ class VortexController < ApplicationController
199
+ before_action :require_authentication!
200
+ include VortexHelper
201
+
202
+ def generate_jwt
203
+ user = current_vortex_user
204
+ extra = params.permit(:componentId, :scope, :scopeType).to_h.compact
205
+
206
+ jwt = vortex_client.generate_jwt(
207
+ user: user,
208
+ attributes: extra.empty? ? nil : extra
209
+ )
210
+
211
+ render json: { jwt: jwt }
212
+ rescue Vortex::VortexError => e
213
+ Rails.logger.error("JWT generation error: #{e.message}")
214
+ render json: { error: 'Internal server error' }, status: :internal_server_error
215
+ end
216
+ end
217
+ ```
218
+
219
+ ### Sinatra:
220
+ ```ruby
221
+ require 'sinatra/base'
222
+ require 'json'
223
+
224
+ class MyApp < Sinatra::Base
225
+ helpers AuthHelper
226
+
227
+ post '/api/vortex/jwt' do
228
+ require_authentication!
229
+
230
+ content_type :json
231
+
232
+ begin
233
+ user = current_vortex_user
234
+ request_body = JSON.parse(request.body.read) rescue {}
235
+
236
+ extra = request_body.slice('componentId', 'scope', 'scopeType').compact
237
+ extra = nil if extra.empty?
238
+
239
+ jwt = vortex.generate_jwt(
240
+ user: user,
241
+ attributes: extra
242
+ )
243
+
244
+ { jwt: jwt }.to_json
245
+ rescue Vortex::VortexError => e
246
+ logger.error("JWT generation error: #{e.message}")
247
+ status 500
248
+ { error: 'Internal server error' }.to_json
249
+ end
250
+ end
251
+ end
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Step 6: Implement Accept Invitations Endpoint (CRITICAL)
257
+
258
+ ### Rails Routes (`config/routes.rb`):
259
+ ```ruby
260
+ Rails.application.routes.draw do
261
+ namespace :api do
262
+ namespace :vortex do
263
+ post 'jwt', to: 'vortex#generate_jwt'
264
+ get 'invitations', to: 'vortex#get_invitations_by_target'
265
+ get 'invitations/:invitation_id', to: 'vortex#get_invitation'
266
+ post 'invitations/accept', to: 'vortex#accept_invitations'
267
+ delete 'invitations/:invitation_id', to: 'vortex#revoke_invitation'
268
+ post 'invitations/:invitation_id/reinvite', to: 'vortex#reinvite'
269
+ end
270
+ end
271
+ end
272
+ ```
273
+
274
+ ### Rails with ActiveRecord:
275
+ ```ruby
276
+ class Api::VortexController < ApplicationController
277
+ before_action :require_authentication!
278
+ include VortexHelper
279
+
280
+ def accept_invitations
281
+ invitation_ids = params[:invitationIds] || []
282
+ user = params[:user]
283
+
284
+ return render json: { error: 'Missing invitationIds or user' }, status: :bad_request if invitation_ids.empty? || !user
285
+
286
+ begin
287
+ # 1. Mark as accepted in Vortex
288
+ vortex_client.accept_invitations(invitation_ids, user)
289
+
290
+ # 2. CRITICAL - Add to database
291
+ ActiveRecord::Base.transaction do
292
+ invitation_ids.each do |invitation_id|
293
+ invitation = vortex_client.get_invitation(invitation_id)
294
+
295
+ (invitation['groups'] || []).each do |group|
296
+ GroupMembership.find_or_create_by!(
297
+ user_id: current_user.id,
298
+ group_type: group['type'],
299
+ group_id: group['groupId']
300
+ ) do |membership|
301
+ membership.role = invitation['role'] || 'member'
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ render json: {
308
+ success: true,
309
+ acceptedCount: invitation_ids.length
310
+ }
311
+ rescue Vortex::VortexError => e
312
+ Rails.logger.error("Accept invitations error: #{e.message}")
313
+ render json: { error: 'Internal server error' }, status: :internal_server_error
314
+ rescue => e
315
+ Rails.logger.error("Database error: #{e.message}")
316
+ render json: { error: 'Internal server error' }, status: :internal_server_error
317
+ end
318
+ end
319
+ end
320
+ ```
321
+
322
+ ### Sinatra with Sequel:
323
+ ```ruby
324
+ post '/api/vortex/invitations/accept' do
325
+ require_authentication!
326
+ content_type :json
327
+
328
+ request_body = JSON.parse(request.body.read)
329
+ invitation_ids = request_body['invitationIds'] || []
330
+ user = request_body['user']
331
+
332
+ halt 400, { error: 'Missing invitationIds or user' }.to_json if invitation_ids.empty? || !user
333
+
334
+ begin
335
+ # 1. Mark as accepted in Vortex
336
+ vortex.accept_invitations(invitation_ids, user)
337
+
338
+ # 2. CRITICAL - Add to database
339
+ DB.transaction do
340
+ invitation_ids.each do |invitation_id|
341
+ invitation = vortex.get_invitation(invitation_id)
342
+
343
+ (invitation['groups'] || []).each do |group|
344
+ GroupMembership.insert_conflict(
345
+ target: [:user_id, :group_type, :group_id],
346
+ update: { role: invitation['role'] || 'member' }
347
+ ).insert(
348
+ user_id: current_user.id,
349
+ group_type: group['type'],
350
+ group_id: group['groupId'],
351
+ role: invitation['role'] || 'member',
352
+ joined_at: Time.now
353
+ )
354
+ end
355
+ end
356
+ end
357
+
358
+ {
359
+ success: true,
360
+ acceptedCount: invitation_ids.length
361
+ }.to_json
362
+ rescue Vortex::VortexError => e
363
+ logger.error("Accept invitations error: #{e.message}")
364
+ status 500
365
+ { error: 'Internal server error' }.to_json
366
+ end
367
+ end
368
+ ```
369
+
370
+ **Critical - Adapt database logic:**
371
+ - Use their actual table/model names (from discovery)
372
+ - Use their actual field names
373
+ - Use their ORM pattern (ActiveRecord, Sequel)
374
+ - Handle duplicate memberships if needed
375
+
376
+ ---
377
+
378
+ ## Step 7: Database Models
379
+
380
+ ### Rails Migration:
381
+ ```ruby
382
+ # db/migrate/YYYYMMDDHHMMSS_create_group_memberships.rb
383
+ class CreateGroupMemberships < ActiveRecord::Migration[7.0]
384
+ def change
385
+ create_table :group_memberships do |t|
386
+ t.string :user_id, null: false
387
+ t.string :group_type, null: false, limit: 100
388
+ t.string :group_id, null: false
389
+ t.string :role, default: 'member', limit: 50
390
+ t.timestamp :joined_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
391
+
392
+ t.index [:user_id, :group_type, :group_id], unique: true, name: 'unique_membership'
393
+ t.index [:group_type, :group_id], name: 'idx_group'
394
+ t.index [:user_id], name: 'idx_user'
395
+ end
396
+ end
397
+ end
398
+ ```
399
+
400
+ ### Rails Model:
401
+ ```ruby
402
+ # app/models/group_membership.rb
403
+ class GroupMembership < ApplicationRecord
404
+ validates :user_id, presence: true
405
+ validates :group_type, presence: true
406
+ validates :group_id, presence: true
407
+ validates :role, presence: true
408
+
409
+ validates :user_id, uniqueness: { scope: [:group_type, :group_id] }
410
+ end
411
+ ```
412
+
413
+ ### Sequel Migration:
414
+ ```ruby
415
+ # db/migrations/001_create_group_memberships.rb
416
+ Sequel.migration do
417
+ change do
418
+ create_table(:group_memberships) do
419
+ primary_key :id
420
+ String :user_id, null: false
421
+ String :group_type, size: 100, null: false
422
+ String :group_id, null: false
423
+ String :role, size: 50, default: 'member'
424
+ DateTime :joined_at, null: false, default: Sequel::CURRENT_TIMESTAMP
425
+
426
+ index [:user_id, :group_type, :group_id], unique: true, name: :unique_membership
427
+ index [:group_type, :group_id], name: :idx_group
428
+ index [:user_id], name: :idx_user
429
+ end
430
+ end
431
+ end
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Step 8: Build and Test
437
+
438
+ ```bash
439
+ # Run migrations
440
+ rails db:migrate # Rails
441
+ sequel -m db/migrations $DATABASE_URL # Sequel
442
+
443
+ # Start server
444
+ rails server # Rails
445
+ bundle exec rackup # Sinatra
446
+
447
+ # Test JWT endpoint
448
+ curl -X POST http://localhost:3000/api/vortex/jwt \
449
+ -H "Authorization: Bearer your-auth-token"
450
+ ```
451
+
452
+ Expected response:
453
+ ```json
454
+ {
455
+ "jwt": "eyJhbGciOiJIUzI1NiIs..."
456
+ }
457
+ ```
458
+
459
+ ---
460
+
461
+ ## Common Errors
462
+
463
+ **"LoadError: cannot load such file -- vortex"** → Run `bundle install`
464
+
465
+ **"VORTEX_API_KEY not set"** → Check `.env` file, credentials, or environment variables
466
+
467
+ **User not added to database** → Must implement database logic in accept handler (see Step 6)
468
+
469
+ **"NoMethodError: undefined method `admin?'"** → Implement admin check in User model
470
+
471
+ **CORS errors** → Add CORS middleware:
472
+
473
+ **Rails:**
474
+ ```ruby
475
+ # Gemfile
476
+ gem 'rack-cors'
477
+
478
+ # config/initializers/cors.rb
479
+ Rails.application.config.middleware.insert_before 0, Rack::Cors do
480
+ allow do
481
+ origins 'http://localhost:3000'
482
+ resource '*', headers: :any, methods: [:get, :post, :delete, :options]
483
+ end
484
+ end
485
+ ```
486
+
487
+ **Sinatra:**
488
+ ```ruby
489
+ require 'sinatra/cross_origin'
490
+
491
+ class MyApp < Sinatra::Base
492
+ register Sinatra::CrossOrigin
493
+
494
+ configure do
495
+ enable :cross_origin
496
+ end
497
+
498
+ options '*' do
499
+ response.headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, OPTIONS'
500
+ response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
501
+ 200
502
+ end
503
+ end
504
+ ```
505
+
506
+ ---
507
+
508
+ ## After Implementation Report
509
+
510
+ List files created/modified:
511
+ - Dependency: Gemfile
512
+ - Client: config/initializers/vortex.rb (or concern)
513
+ - 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
516
+
517
+ Confirm:
518
+ - Vortex gem installed
519
+ - VortexClient instance created
520
+ - JWT endpoint returns valid JWT
521
+ - Accept invitations includes database logic
522
+ - Routes registered at correct prefix
523
+ - Migrations run
524
+
525
+ ## Endpoints Registered
526
+
527
+ All endpoints at `/api/vortex`:
528
+ - `POST /jwt` - Generate JWT for authenticated user
529
+ - `GET /invitations` - Get invitations by target
530
+ - `GET /invitations/:id` - Get invitation by ID
531
+ - `POST /invitations/accept` - Accept invitations (custom DB logic)
532
+ - `DELETE /invitations/:id` - Revoke invitation
533
+ - `POST /invitations/:id/reinvite` - Resend invitation
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Vortex Ruby SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.3] - 2025-01-29
9
+
10
+ ### Added
11
+ - **ACCEPT_USER Type**: New preferred format for accepting invitations with `email`, `phone`, and `name` fields
12
+ - Enhanced `accept_invitations` method to support both new User hash format and legacy target format
13
+
14
+ ### Changed
15
+ - **DEPRECATED**: Legacy target hash format for `accept_invitations` - use User hash instead
16
+ - Internal API calls now always use User format for consistency
17
+ - Added warning messages when legacy target format is used
18
+
19
+ ### Fixed
20
+ - Maintained 100% backward compatibility - existing code using legacy target format continues to work
data/PUBLISHING.md CHANGED
@@ -76,7 +76,7 @@ Create or update `CHANGELOG.md` with release notes:
76
76
  ### Step 4: Install Dependencies and Test
77
77
 
78
78
  ```bash
79
- cd packages/vortex-ruby-sdk
79
+ cd sdks/vortex-ruby-sdk
80
80
 
81
81
  # Install dependencies
82
82
  bundle install
@@ -182,29 +182,29 @@ jobs:
182
182
 
183
183
  - name: Install dependencies
184
184
  run: |
185
- cd packages/vortex-ruby-sdk
185
+ cd sdks/vortex-ruby-sdk
186
186
  bundle install
187
187
 
188
188
  - name: Run tests
189
189
  run: |
190
- cd packages/vortex-ruby-sdk
190
+ cd sdks/vortex-ruby-sdk
191
191
  bundle exec rspec
192
192
 
193
193
  - name: Run RuboCop
194
194
  run: |
195
- cd packages/vortex-ruby-sdk
195
+ cd sdks/vortex-ruby-sdk
196
196
  bundle exec rubocop
197
197
 
198
198
  - name: Build gem
199
199
  run: |
200
- cd packages/vortex-ruby-sdk
200
+ cd sdks/vortex-ruby-sdk
201
201
  gem build vortex-ruby-sdk.gemspec
202
202
 
203
203
  - name: Publish to RubyGems
204
204
  env:
205
205
  GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
206
206
  run: |
207
- cd packages/vortex-ruby-sdk
207
+ cd sdks/vortex-ruby-sdk
208
208
  gem push *.gem
209
209
  ```
210
210
 
data/lib/vortex/client.rb CHANGED
@@ -146,16 +146,84 @@ module Vortex
146
146
  raise VortexError, "Failed to revoke invitation: #{e.message}"
147
147
  end
148
148
 
149
- # Accept invitations
149
+ # Accept invitations using the new User format (preferred)
150
+ #
151
+ # Supports three formats:
152
+ # 1. User hash (preferred): { email: '...', phone: '...', name: '...' }
153
+ # 2. Target hash (deprecated): { type: 'email', value: '...' }
154
+ # 3. Array of targets (deprecated): [{ type: 'email', value: '...' }, ...]
150
155
  #
151
156
  # @param invitation_ids [Array<String>] List of invitation IDs to accept
152
- # @param target [Hash] Target hash with :type and :value
157
+ # @param user_or_target [Hash, Array] User hash with :email/:phone/:name keys, OR legacy target(s)
153
158
  # @return [Hash] The accepted invitation result
154
159
  # @raise [VortexError] If the request fails
155
- def accept_invitations(invitation_ids, target)
160
+ #
161
+ # @example New format (preferred)
162
+ # user = { email: 'user@example.com', name: 'John Doe' }
163
+ # result = client.accept_invitations(['inv-123'], user)
164
+ #
165
+ # @example Legacy format (deprecated)
166
+ # target = { type: 'email', value: 'user@example.com' }
167
+ # result = client.accept_invitations(['inv-123'], target)
168
+ def accept_invitations(invitation_ids, user_or_target)
169
+ # Check if it's an array of targets (legacy format with multiple targets)
170
+ if user_or_target.is_a?(Array)
171
+ warn '[Vortex SDK] DEPRECATED: Passing an array of targets is deprecated. ' \
172
+ 'Use the User format instead: accept_invitations(invitation_ids, { email: "user@example.com" })'
173
+
174
+ raise VortexError, 'No targets provided' if user_or_target.empty?
175
+
176
+ last_result = nil
177
+ last_exception = nil
178
+
179
+ user_or_target.each do |target|
180
+ begin
181
+ last_result = accept_invitations(invitation_ids, target)
182
+ rescue => e
183
+ last_exception = e
184
+ end
185
+ end
186
+
187
+ raise last_exception if last_exception
188
+
189
+ return last_result || {}
190
+ end
191
+
192
+ # Check if it's a legacy target format (has :type and :value keys)
193
+ is_legacy_target = user_or_target.key?(:type) && user_or_target.key?(:value)
194
+
195
+ if is_legacy_target
196
+ warn '[Vortex SDK] DEPRECATED: Passing a target hash is deprecated. ' \
197
+ 'Use the User format instead: accept_invitations(invitation_ids, { email: "user@example.com" })'
198
+
199
+ # Convert target to User format
200
+ target_type = user_or_target[:type]
201
+ target_value = user_or_target[:value]
202
+
203
+ user = {}
204
+ case target_type
205
+ when 'email'
206
+ user[:email] = target_value
207
+ when 'sms', 'phoneNumber'
208
+ user[:phone] = target_value
209
+ else
210
+ # For other types, try to use as email
211
+ user[:email] = target_value
212
+ end
213
+
214
+ # Recursively call with User format
215
+ return accept_invitations(invitation_ids, user)
216
+ end
217
+
218
+ # New User format
219
+ user = user_or_target
220
+
221
+ # Validate that either email or phone is provided
222
+ raise VortexError, 'User must have either email or phone' if user[:email].nil? && user[:phone].nil?
223
+
156
224
  body = {
157
225
  invitationIds: invitation_ids,
158
- target: target
226
+ user: user.compact # Remove nil values
159
227
  }
160
228
 
161
229
  response = @connection.post('/api/v1/invitations/accept') do |req|
@@ -164,6 +232,8 @@ module Vortex
164
232
  end
165
233
 
166
234
  handle_response(response)
235
+ rescue VortexError
236
+ raise
167
237
  rescue => e
168
238
  raise VortexError, "Failed to accept invitations: #{e.message}"
169
239
  end
data/lib/vortex/types.rb CHANGED
@@ -39,6 +39,19 @@ module Vortex
39
39
  createdAt: String # ISO 8601 timestamp when the group was created
40
40
  }.freeze
41
41
 
42
+ # AcceptUser structure for accepting invitations (new format - preferred)
43
+ # @example
44
+ # {
45
+ # email: 'user@example.com',
46
+ # phone: '+1234567890', # Optional
47
+ # name: 'John Doe' # Optional
48
+ # }
49
+ ACCEPT_USER = {
50
+ email: String, # Optional but either email or phone must be provided
51
+ phone: String, # Optional but either email or phone must be provided
52
+ name: String # Optional
53
+ }.freeze
54
+
42
55
  # Invitation structure from API responses
43
56
  # @example
44
57
  # {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vortex
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.3'
5
5
  end
data/lib/vortex.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'vortex/version'
4
4
  require 'vortex/error'
5
+ require 'vortex/types'
5
6
  require 'vortex/client'
6
7
 
7
8
  # Vortex Ruby SDK
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = 'vortex-ruby-sdk'
7
7
  spec.version = Vortex::VERSION
8
8
  spec.authors = ['Vortex Software']
9
- spec.email = ['support@vortexsoftware.io']
9
+ spec.email = ['support@vortexsoftware.com']
10
10
 
11
11
  spec.summary = 'Ruby SDK for Vortex invitation system'
12
12
  spec.description = 'A Ruby SDK that provides seamless integration with the Vortex invitation system, including JWT generation and invitation management.'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vortex-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vortex Software
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-12-16 00:00:00.000000000 Z
11
+ date: 2026-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -139,11 +139,13 @@ dependencies:
139
139
  description: A Ruby SDK that provides seamless integration with the Vortex invitation
140
140
  system, including JWT generation and invitation management.
141
141
  email:
142
- - support@vortexsoftware.io
142
+ - support@vortexsoftware.com
143
143
  executables: []
144
144
  extensions: []
145
145
  extra_rdoc_files: []
146
146
  files:
147
+ - ".claude/implementation-guide.md"
148
+ - CHANGELOG.md
147
149
  - LICENSE
148
150
  - PUBLISHING.md
149
151
  - README.md