vortex-ruby-sdk 1.0.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: 18819ec338fa097c24046e0a535a98483acbe55e0163fe6e6db86d0160334a1b
4
- data.tar.gz: 1cdbc41d41372b4658ef5a38db7ee6cbda22f5c63227738bd8ac79140215af43
3
+ metadata.gz: c2f54f415260bef01b6106dc5d32cf274e4b9c45e00cdd89e18ad6dfbc1df177
4
+ data.tar.gz: 32e1ab2a8a7bc1ee1a3a683d82832a301f4e3403289a64ff8eea4c77410dc104
5
5
  SHA512:
6
- metadata.gz: 950cf7e0435224d9e7aa534d94b3999ed72f823b2211eb368a332de4fe683f8c0998bbde59a445571b5b7fb81cc5de60036944c7944a124f4933ea0aae41a698
7
- data.tar.gz: 561c091c7232a282cbe854dacdc3d2e3eaac70fb28a7b6c08dee852d4b7466d56f2fe4c07e7834cfe1e23c2802786c5019d6cf514f720ac3341fa5827085fa92
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