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 +4 -4
- data/.claude/implementation-guide.md +533 -0
- data/CHANGELOG.md +20 -0
- data/PUBLISHING.md +6 -6
- data/README.md +64 -21
- data/examples/basic_usage.rb +19 -13
- data/examples/rails_app.rb +10 -11
- data/examples/sinatra_app.rb +10 -11
- data/lib/vortex/client.rb +113 -19
- data/lib/vortex/rails.rb +56 -7
- data/lib/vortex/types.rb +87 -0
- data/lib/vortex/version.rb +1 -1
- data/lib/vortex.rb +1 -0
- data/vortex-ruby-sdk.gemspec +1 -1
- metadata +10 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2f54f415260bef01b6106dc5d32cf274e4b9c45e00cdd89e18ad6dfbc1df177
|
|
4
|
+
data.tar.gz: 32e1ab2a8a7bc1ee1a3a683d82832a301f4e3403289a64ff8eea4c77410dc104
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
185
|
+
cd sdks/vortex-ruby-sdk
|
|
186
186
|
bundle install
|
|
187
187
|
|
|
188
188
|
- name: Run tests
|
|
189
189
|
run: |
|
|
190
|
-
cd
|
|
190
|
+
cd sdks/vortex-ruby-sdk
|
|
191
191
|
bundle exec rspec
|
|
192
192
|
|
|
193
193
|
- name: Run RuboCop
|
|
194
194
|
run: |
|
|
195
|
-
cd
|
|
195
|
+
cd sdks/vortex-ruby-sdk
|
|
196
196
|
bundle exec rubocop
|
|
197
197
|
|
|
198
198
|
- name: Build gem
|
|
199
199
|
run: |
|
|
200
|
-
cd
|
|
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
|
|
207
|
+
cd sdks/vortex-ruby-sdk
|
|
208
208
|
gem push *.gem
|
|
209
209
|
```
|
|
210
210
|
|