vortex-ruby-sdk 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/PUBLISHING.md +474 -0
- data/README.md +204 -0
- data/Rakefile +40 -0
- data/examples/basic_usage.rb +72 -0
- data/examples/rails_app.rb +129 -0
- data/examples/sinatra_app.rb +119 -0
- data/lib/vortex/client.rb +229 -0
- data/lib/vortex/error.rb +19 -0
- data/lib/vortex/rails.rb +257 -0
- data/lib/vortex/sinatra.rb +276 -0
- data/lib/vortex/version.rb +5 -0
- data/lib/vortex.rb +55 -0
- data/vortex-ruby-sdk.gemspec +47 -0
- metadata +188 -0
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
require 'yard'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
RuboCop::RakeTask.new(:rubocop)
|
10
|
+
YARD::Rake::YardocTask.new
|
11
|
+
|
12
|
+
desc 'Run all tests and linting'
|
13
|
+
task test: [:spec, :rubocop]
|
14
|
+
|
15
|
+
desc 'Run tests with coverage'
|
16
|
+
task :coverage do
|
17
|
+
ENV['COVERAGE'] = 'true'
|
18
|
+
Rake::Task['spec'].execute
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Generate documentation'
|
22
|
+
task :docs do
|
23
|
+
Rake::Task['yard'].execute
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Setup development environment'
|
27
|
+
task :setup do
|
28
|
+
sh 'bundle install'
|
29
|
+
puts 'Development environment ready!'
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Run interactive console'
|
33
|
+
task :console do
|
34
|
+
require 'bundler/setup'
|
35
|
+
require 'vortex'
|
36
|
+
require 'irb'
|
37
|
+
IRB.start(__FILE__)
|
38
|
+
end
|
39
|
+
|
40
|
+
task default: :test
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Basic usage example for Vortex Ruby SDK
|
5
|
+
#
|
6
|
+
# This example demonstrates how to use the core functionality of the Vortex Ruby SDK,
|
7
|
+
# showing the same operations available in all other Vortex SDKs (Node.js, Python, Java, Go).
|
8
|
+
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'vortex'
|
11
|
+
|
12
|
+
# Initialize the client with your API key
|
13
|
+
API_KEY = ENV['VORTEX_API_KEY'] || 'your-api-key-here'
|
14
|
+
client = Vortex::Client.new(API_KEY)
|
15
|
+
|
16
|
+
# Example user data - same structure as other SDKs
|
17
|
+
user_data = {
|
18
|
+
user_id: 'user123',
|
19
|
+
identifiers: [
|
20
|
+
{ type: 'email', value: 'user@example.com' }
|
21
|
+
],
|
22
|
+
groups: [
|
23
|
+
{ id: 'team1', type: 'team', name: 'Engineering Team' },
|
24
|
+
{ id: 'dept1', type: 'department', name: 'Product Development' }
|
25
|
+
],
|
26
|
+
role: 'admin'
|
27
|
+
}
|
28
|
+
|
29
|
+
begin
|
30
|
+
puts "=== Vortex Ruby SDK Example ==="
|
31
|
+
puts
|
32
|
+
|
33
|
+
# 1. Generate JWT
|
34
|
+
puts "1. Generating JWT for user..."
|
35
|
+
jwt = client.generate_jwt(**user_data)
|
36
|
+
puts "JWT generated: #{jwt[0..50]}..."
|
37
|
+
puts
|
38
|
+
|
39
|
+
# 2. Get invitations by target
|
40
|
+
puts "2. Getting invitations by email target..."
|
41
|
+
invitations = client.get_invitations_by_target('email', 'user@example.com')
|
42
|
+
puts "Found #{invitations.length} invitation(s)"
|
43
|
+
puts
|
44
|
+
|
45
|
+
# 3. Get invitations by group
|
46
|
+
puts "3. Getting invitations for team group..."
|
47
|
+
group_invitations = client.get_invitations_by_group('team', 'team1')
|
48
|
+
puts "Found #{group_invitations.length} group invitation(s)"
|
49
|
+
puts
|
50
|
+
|
51
|
+
# 4. Example of accepting invitations (if any exist)
|
52
|
+
if invitations.any?
|
53
|
+
puts "4. Accepting first invitation..."
|
54
|
+
invitation_ids = [invitations.first['id']]
|
55
|
+
target = { type: 'email', value: 'user@example.com' }
|
56
|
+
|
57
|
+
result = client.accept_invitations(invitation_ids, target)
|
58
|
+
puts "Invitation accepted: #{result['id']}"
|
59
|
+
else
|
60
|
+
puts "4. No invitations to accept"
|
61
|
+
end
|
62
|
+
puts
|
63
|
+
|
64
|
+
puts "=== All operations completed successfully! ==="
|
65
|
+
|
66
|
+
rescue Vortex::VortexError => e
|
67
|
+
puts "Vortex error: #{e.message}"
|
68
|
+
exit 1
|
69
|
+
rescue => e
|
70
|
+
puts "Unexpected error: #{e.message}"
|
71
|
+
exit 1
|
72
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Rails application example for Vortex Ruby SDK
|
5
|
+
#
|
6
|
+
# This example shows how to integrate the Vortex SDK with a Rails application,
|
7
|
+
# providing the same API routes as other SDK integrations (Express, Java Spring, Python Flask).
|
8
|
+
|
9
|
+
# Gemfile additions needed:
|
10
|
+
# gem 'rails', '~> 7.0'
|
11
|
+
# gem 'vortex-ruby-sdk'
|
12
|
+
|
13
|
+
require 'rails'
|
14
|
+
require 'action_controller/railtie'
|
15
|
+
require 'vortex/rails'
|
16
|
+
|
17
|
+
class VortexExampleApp < Rails::Application
|
18
|
+
config.api_only = true
|
19
|
+
config.eager_load = false
|
20
|
+
config.logger = Logger.new(STDOUT)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Example Vortex controller with authentication
|
24
|
+
class VortexController < ActionController::Base
|
25
|
+
include Vortex::Rails::Controller
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Implement user authentication - return user data hash or nil
|
30
|
+
def authenticate_vortex_user
|
31
|
+
# Example: get user from session/JWT/etc.
|
32
|
+
user_id = session[:user_id] || request.headers['X-User-ID']
|
33
|
+
return nil unless user_id
|
34
|
+
|
35
|
+
# Return user data in the format expected by Vortex
|
36
|
+
{
|
37
|
+
user_id: user_id,
|
38
|
+
identifiers: [
|
39
|
+
{ type: 'email', value: session[:user_email] || 'user@example.com' }
|
40
|
+
],
|
41
|
+
groups: [
|
42
|
+
{ id: 'team1', type: 'team', name: 'Engineering' }
|
43
|
+
],
|
44
|
+
role: session[:user_role] || 'user'
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Implement authorization - return true/false
|
49
|
+
def authorize_vortex_operation(operation, user)
|
50
|
+
# Example: check user permissions
|
51
|
+
case operation
|
52
|
+
when 'JWT'
|
53
|
+
true # Everyone can generate JWT if authenticated
|
54
|
+
when 'GET_INVITATIONS', 'GET_INVITATION'
|
55
|
+
true # Everyone can view invitations
|
56
|
+
when 'ACCEPT_INVITATIONS'
|
57
|
+
true # Everyone can accept invitations
|
58
|
+
when 'REVOKE_INVITATION', 'DELETE_GROUP_INVITATIONS'
|
59
|
+
user[:role] == 'admin' # Only admins can delete
|
60
|
+
when 'GET_GROUP_INVITATIONS', 'REINVITE'
|
61
|
+
user[:role] == 'admin' # Only admins can manage groups
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Provide Vortex client instance
|
68
|
+
def vortex_client
|
69
|
+
@vortex_client ||= Vortex::Client.new(
|
70
|
+
ENV['VORTEX_API_KEY'] || 'your-api-key-here'
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Configure routes - these match exactly with other SDK routes
|
76
|
+
Rails.application.routes.draw do
|
77
|
+
scope '/api/vortex', controller: 'vortex' do
|
78
|
+
post 'jwt', action: 'generate_jwt'
|
79
|
+
get 'invitations', action: 'get_invitations_by_target'
|
80
|
+
get 'invitations/:invitation_id', action: 'get_invitation'
|
81
|
+
delete 'invitations/:invitation_id', action: 'revoke_invitation'
|
82
|
+
post 'invitations/accept', action: 'accept_invitations'
|
83
|
+
get 'invitations/by-group/:group_type/:group_id', action: 'get_invitations_by_group'
|
84
|
+
delete 'invitations/by-group/:group_type/:group_id', action: 'delete_invitations_by_group'
|
85
|
+
post 'invitations/:invitation_id/reinvite', action: 'reinvite'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Health check
|
89
|
+
get '/health', to: proc { [200, {}, ['OK']] }
|
90
|
+
|
91
|
+
# Root route with API documentation
|
92
|
+
root to: proc do
|
93
|
+
[200, { 'Content-Type' => 'application/json' }, [
|
94
|
+
{
|
95
|
+
name: 'Vortex Rails API',
|
96
|
+
version: Vortex::VERSION,
|
97
|
+
endpoints: {
|
98
|
+
jwt: 'POST /api/vortex/jwt',
|
99
|
+
invitations: 'GET /api/vortex/invitations?targetType=email&targetValue=user@example.com',
|
100
|
+
invitation: 'GET /api/vortex/invitations/:id',
|
101
|
+
revoke: 'DELETE /api/vortex/invitations/:id',
|
102
|
+
accept: 'POST /api/vortex/invitations/accept',
|
103
|
+
group_invitations: 'GET /api/vortex/invitations/by-group/:type/:id',
|
104
|
+
delete_group: 'DELETE /api/vortex/invitations/by-group/:type/:id',
|
105
|
+
reinvite: 'POST /api/vortex/invitations/:id/reinvite'
|
106
|
+
}
|
107
|
+
}.to_json
|
108
|
+
]]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if __FILE__ == $0
|
113
|
+
puts "🚀 Starting Vortex Rails API server..."
|
114
|
+
puts "📊 Health check: http://localhost:3000/health"
|
115
|
+
puts "🔧 Vortex API routes available at http://localhost:3000/api/vortex"
|
116
|
+
puts
|
117
|
+
puts "Available endpoints:"
|
118
|
+
puts " POST /api/vortex/jwt"
|
119
|
+
puts " GET /api/vortex/invitations?targetType=email&targetValue=user@example.com"
|
120
|
+
puts " GET /api/vortex/invitations/:id"
|
121
|
+
puts " DELETE /api/vortex/invitations/:id"
|
122
|
+
puts " POST /api/vortex/invitations/accept"
|
123
|
+
puts " GET /api/vortex/invitations/by-group/:type/:id"
|
124
|
+
puts " DELETE /api/vortex/invitations/by-group/:type/:id"
|
125
|
+
puts " POST /api/vortex/invitations/:id/reinvite"
|
126
|
+
|
127
|
+
Rails.application.initialize!
|
128
|
+
Rails.application.run(Port: 3000)
|
129
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Sinatra application example for Vortex Ruby SDK
|
5
|
+
#
|
6
|
+
# This example shows how to integrate the Vortex SDK with a Sinatra application,
|
7
|
+
# providing the same API routes as other SDK integrations (Express, Java Spring, Python Flask).
|
8
|
+
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'sinatra/base'
|
11
|
+
require 'json'
|
12
|
+
require 'vortex/sinatra'
|
13
|
+
|
14
|
+
class VortexSinatraApp < Sinatra::Base
|
15
|
+
register Vortex::Sinatra
|
16
|
+
|
17
|
+
configure do
|
18
|
+
set :vortex_api_key, ENV['VORTEX_API_KEY'] || 'your-api-key-here'
|
19
|
+
set :vortex_base_url, ENV['VORTEX_BASE_URL'] # optional
|
20
|
+
end
|
21
|
+
|
22
|
+
# Implement user authentication
|
23
|
+
def authenticate_vortex_user
|
24
|
+
# Example: get user from headers/session/JWT
|
25
|
+
user_id = request.env['HTTP_X_USER_ID'] || 'demo-user'
|
26
|
+
return nil unless user_id
|
27
|
+
|
28
|
+
# Return user data in the format expected by Vortex
|
29
|
+
{
|
30
|
+
user_id: user_id,
|
31
|
+
identifiers: [
|
32
|
+
{ type: 'email', value: request.env['HTTP_X_USER_EMAIL'] || 'demo@example.com' }
|
33
|
+
],
|
34
|
+
groups: [
|
35
|
+
{ id: 'team1', type: 'team', name: 'Engineering' }
|
36
|
+
],
|
37
|
+
role: request.env['HTTP_X_USER_ROLE'] || 'user'
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Implement authorization
|
42
|
+
def authorize_vortex_operation(operation, user)
|
43
|
+
case operation
|
44
|
+
when 'JWT'
|
45
|
+
true # Everyone can generate JWT if authenticated
|
46
|
+
when 'GET_INVITATIONS', 'GET_INVITATION'
|
47
|
+
true # Everyone can view invitations
|
48
|
+
when 'ACCEPT_INVITATIONS'
|
49
|
+
true # Everyone can accept invitations
|
50
|
+
when 'REVOKE_INVITATION', 'DELETE_GROUP_INVITATIONS'
|
51
|
+
user[:role] == 'admin' # Only admins can delete
|
52
|
+
when 'GET_GROUP_INVITATIONS', 'REINVITE'
|
53
|
+
user[:role] == 'admin' # Only admins can manage groups
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Health check endpoint
|
60
|
+
get '/health' do
|
61
|
+
content_type :json
|
62
|
+
{ status: 'OK', version: Vortex::VERSION }.to_json
|
63
|
+
end
|
64
|
+
|
65
|
+
# Root endpoint with API documentation
|
66
|
+
get '/' do
|
67
|
+
content_type :json
|
68
|
+
{
|
69
|
+
name: 'Vortex Sinatra API',
|
70
|
+
version: Vortex::VERSION,
|
71
|
+
endpoints: {
|
72
|
+
jwt: 'POST /api/vortex/jwt',
|
73
|
+
invitations: 'GET /api/vortex/invitations?targetType=email&targetValue=user@example.com',
|
74
|
+
invitation: 'GET /api/vortex/invitations/:id',
|
75
|
+
revoke: 'DELETE /api/vortex/invitations/:id',
|
76
|
+
accept: 'POST /api/vortex/invitations/accept',
|
77
|
+
group_invitations: 'GET /api/vortex/invitations/by-group/:type/:id',
|
78
|
+
delete_group: 'DELETE /api/vortex/invitations/by-group/:type/:id',
|
79
|
+
reinvite: 'POST /api/vortex/invitations/:id/reinvite'
|
80
|
+
}
|
81
|
+
}.to_json
|
82
|
+
end
|
83
|
+
|
84
|
+
# Error handlers
|
85
|
+
error Vortex::VortexError do
|
86
|
+
content_type :json
|
87
|
+
status 500
|
88
|
+
{ error: env['sinatra.error'].message }.to_json
|
89
|
+
end
|
90
|
+
|
91
|
+
error do
|
92
|
+
content_type :json
|
93
|
+
status 500
|
94
|
+
{ error: 'Internal server error' }.to_json
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if __FILE__ == $0
|
99
|
+
puts "🚀 Starting Vortex Sinatra API server..."
|
100
|
+
puts "📊 Health check: http://localhost:4567/health"
|
101
|
+
puts "🔧 Vortex API routes available at http://localhost:4567/api/vortex"
|
102
|
+
puts
|
103
|
+
puts "Available endpoints:"
|
104
|
+
puts " POST /api/vortex/jwt"
|
105
|
+
puts " GET /api/vortex/invitations?targetType=email&targetValue=user@example.com"
|
106
|
+
puts " GET /api/vortex/invitations/:id"
|
107
|
+
puts " DELETE /api/vortex/invitations/:id"
|
108
|
+
puts " POST /api/vortex/invitations/accept"
|
109
|
+
puts " GET /api/vortex/invitations/by-group/:type/:id"
|
110
|
+
puts " DELETE /api/vortex/invitations/by-group/:type/:id"
|
111
|
+
puts " POST /api/vortex/invitations/:id/reinvite"
|
112
|
+
puts
|
113
|
+
puts "Authentication headers (for testing):"
|
114
|
+
puts " X-User-ID: your-user-id"
|
115
|
+
puts " X-User-Email: your-email@example.com"
|
116
|
+
puts " X-User-Role: user|admin"
|
117
|
+
|
118
|
+
VortexSinatraApp.run!
|
119
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
require 'openssl'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'faraday'
|
8
|
+
|
9
|
+
module Vortex
|
10
|
+
# Vortex API client for Ruby
|
11
|
+
#
|
12
|
+
# Provides the same functionality as other Vortex SDKs with JWT generation,
|
13
|
+
# invitation management, and full API compatibility.
|
14
|
+
class Client
|
15
|
+
# Base URL for Vortex API
|
16
|
+
DEFAULT_BASE_URL = 'https://api.vortexsoftware.com'
|
17
|
+
|
18
|
+
# @param api_key [String] Your Vortex API key
|
19
|
+
# @param base_url [String] Custom base URL (optional)
|
20
|
+
def initialize(api_key, base_url: nil)
|
21
|
+
@api_key = api_key
|
22
|
+
@base_url = base_url || DEFAULT_BASE_URL
|
23
|
+
@connection = build_connection
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generate a JWT for the given user data
|
27
|
+
#
|
28
|
+
# This uses the exact same algorithm as the Node.js SDK to ensure
|
29
|
+
# complete compatibility across all platforms.
|
30
|
+
#
|
31
|
+
# @param user_id [String] Unique identifier for the user
|
32
|
+
# @param identifiers [Array<Hash>] Array of identifier hashes with :type and :value
|
33
|
+
# @param groups [Array<Hash>] Array of group hashes with :id, :type, and :name
|
34
|
+
# @param role [String, nil] Optional user role
|
35
|
+
# @return [String] JWT token
|
36
|
+
# @raise [VortexError] If API key is invalid or JWT generation fails
|
37
|
+
def generate_jwt(user_id:, identifiers:, groups:, role: nil)
|
38
|
+
# Parse API key - same format as Node.js SDK
|
39
|
+
prefix, encoded_id, key = @api_key.split('.')
|
40
|
+
|
41
|
+
raise VortexError, 'Invalid API key format' unless prefix && encoded_id && key
|
42
|
+
raise VortexError, 'Invalid API key prefix' unless prefix == 'VRTX'
|
43
|
+
|
44
|
+
# Decode the ID from base64url (same as Node.js Buffer.from(encodedId, 'base64url'))
|
45
|
+
decoded_bytes = Base64.urlsafe_decode64(encoded_id)
|
46
|
+
|
47
|
+
# Convert to UUID string format (same as uuidStringify in Node.js)
|
48
|
+
id = format_uuid(decoded_bytes)
|
49
|
+
|
50
|
+
expires = Time.now.to_i + 3600
|
51
|
+
|
52
|
+
# Step 1: Derive signing key from API key + ID (same as Node.js)
|
53
|
+
signing_key = OpenSSL::HMAC.digest('sha256', key, id)
|
54
|
+
|
55
|
+
# Step 2: Build header + payload (same structure as Node.js)
|
56
|
+
header = {
|
57
|
+
iat: Time.now.to_i,
|
58
|
+
alg: 'HS256',
|
59
|
+
typ: 'JWT',
|
60
|
+
kid: id
|
61
|
+
}
|
62
|
+
|
63
|
+
payload = {
|
64
|
+
userId: user_id,
|
65
|
+
groups: groups,
|
66
|
+
role: role,
|
67
|
+
expires: expires,
|
68
|
+
identifiers: identifiers
|
69
|
+
}
|
70
|
+
|
71
|
+
# Step 3: Base64URL encode (same as Node.js)
|
72
|
+
header_b64 = base64url_encode(JSON.generate(header))
|
73
|
+
payload_b64 = base64url_encode(JSON.generate(payload))
|
74
|
+
|
75
|
+
# Step 4: Sign with HMAC-SHA256 (same as Node.js)
|
76
|
+
signature = OpenSSL::HMAC.digest('sha256', signing_key, "#{header_b64}.#{payload_b64}")
|
77
|
+
signature_b64 = base64url_encode(signature)
|
78
|
+
|
79
|
+
"#{header_b64}.#{payload_b64}.#{signature_b64}"
|
80
|
+
rescue => e
|
81
|
+
raise VortexError, "JWT generation failed: #{e.message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get invitations by target
|
85
|
+
#
|
86
|
+
# @param target_type [String] Type of target (email, sms)
|
87
|
+
# @param target_value [String] Value of target (email address, phone number)
|
88
|
+
# @return [Array<Hash>] List of invitations
|
89
|
+
# @raise [VortexError] If the request fails
|
90
|
+
def get_invitations_by_target(target_type, target_value)
|
91
|
+
response = @connection.get('/api/v1/invitations') do |req|
|
92
|
+
req.params['targetType'] = target_type
|
93
|
+
req.params['targetValue'] = target_value
|
94
|
+
end
|
95
|
+
|
96
|
+
handle_response(response)['invitations'] || []
|
97
|
+
rescue => e
|
98
|
+
raise VortexError, "Failed to get invitations by target: #{e.message}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get a specific invitation by ID
|
102
|
+
#
|
103
|
+
# @param invitation_id [String] The invitation ID
|
104
|
+
# @return [Hash] The invitation data
|
105
|
+
# @raise [VortexError] If the request fails
|
106
|
+
def get_invitation(invitation_id)
|
107
|
+
response = @connection.get("/api/v1/invitations/#{invitation_id}")
|
108
|
+
handle_response(response)
|
109
|
+
rescue => e
|
110
|
+
raise VortexError, "Failed to get invitation: #{e.message}"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Revoke (delete) an invitation
|
114
|
+
#
|
115
|
+
# @param invitation_id [String] The invitation ID to revoke
|
116
|
+
# @return [Hash] Success response
|
117
|
+
# @raise [VortexError] If the request fails
|
118
|
+
def revoke_invitation(invitation_id)
|
119
|
+
response = @connection.delete("/api/v1/invitations/#{invitation_id}")
|
120
|
+
handle_response(response)
|
121
|
+
rescue => e
|
122
|
+
raise VortexError, "Failed to revoke invitation: #{e.message}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Accept invitations
|
126
|
+
#
|
127
|
+
# @param invitation_ids [Array<String>] List of invitation IDs to accept
|
128
|
+
# @param target [Hash] Target hash with :type and :value
|
129
|
+
# @return [Hash] The accepted invitation result
|
130
|
+
# @raise [VortexError] If the request fails
|
131
|
+
def accept_invitations(invitation_ids, target)
|
132
|
+
body = {
|
133
|
+
invitationIds: invitation_ids,
|
134
|
+
target: target
|
135
|
+
}
|
136
|
+
|
137
|
+
response = @connection.post('/api/v1/invitations/accept') do |req|
|
138
|
+
req.headers['Content-Type'] = 'application/json'
|
139
|
+
req.body = JSON.generate(body)
|
140
|
+
end
|
141
|
+
|
142
|
+
handle_response(response)
|
143
|
+
rescue => e
|
144
|
+
raise VortexError, "Failed to accept invitations: #{e.message}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Get invitations by group
|
148
|
+
#
|
149
|
+
# @param group_type [String] The group type
|
150
|
+
# @param group_id [String] The group ID
|
151
|
+
# @return [Array<Hash>] List of invitations for the group
|
152
|
+
# @raise [VortexError] If the request fails
|
153
|
+
def get_invitations_by_group(group_type, group_id)
|
154
|
+
response = @connection.get("/api/v1/invitations/by-group/#{group_type}/#{group_id}")
|
155
|
+
result = handle_response(response)
|
156
|
+
result['invitations'] || []
|
157
|
+
rescue => e
|
158
|
+
raise VortexError, "Failed to get group invitations: #{e.message}"
|
159
|
+
end
|
160
|
+
|
161
|
+
# Delete invitations by group
|
162
|
+
#
|
163
|
+
# @param group_type [String] The group type
|
164
|
+
# @param group_id [String] The group ID
|
165
|
+
# @return [Hash] Success response
|
166
|
+
# @raise [VortexError] If the request fails
|
167
|
+
def delete_invitations_by_group(group_type, group_id)
|
168
|
+
response = @connection.delete("/api/v1/invitations/by-group/#{group_type}/#{group_id}")
|
169
|
+
handle_response(response)
|
170
|
+
rescue => e
|
171
|
+
raise VortexError, "Failed to delete group invitations: #{e.message}"
|
172
|
+
end
|
173
|
+
|
174
|
+
# Reinvite a user
|
175
|
+
#
|
176
|
+
# @param invitation_id [String] The invitation ID to reinvite
|
177
|
+
# @return [Hash] The reinvited invitation result
|
178
|
+
# @raise [VortexError] If the request fails
|
179
|
+
def reinvite(invitation_id)
|
180
|
+
response = @connection.post("/api/v1/invitations/#{invitation_id}/reinvite")
|
181
|
+
handle_response(response)
|
182
|
+
rescue => e
|
183
|
+
raise VortexError, "Failed to reinvite: #{e.message}"
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def build_connection
|
189
|
+
Faraday.new(@base_url) do |conn|
|
190
|
+
conn.request :json
|
191
|
+
conn.response :json, content_type: /\bjson$/
|
192
|
+
conn.adapter Faraday.default_adapter
|
193
|
+
|
194
|
+
# Add API key header (same as Node.js SDK)
|
195
|
+
conn.headers['x-api-key'] = @api_key
|
196
|
+
conn.headers['User-Agent'] = "vortex-ruby-sdk/#{Vortex::VERSION}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def handle_response(response)
|
201
|
+
case response.status
|
202
|
+
when 200..299
|
203
|
+
response.body || {}
|
204
|
+
when 400..499
|
205
|
+
error_msg = response.body.is_a?(Hash) ? response.body['error'] || response.body['message'] : 'Client error'
|
206
|
+
raise VortexError, "Client error (#{response.status}): #{error_msg}"
|
207
|
+
when 500..599
|
208
|
+
error_msg = response.body.is_a?(Hash) ? response.body['error'] || response.body['message'] : 'Server error'
|
209
|
+
raise VortexError, "Server error (#{response.status}): #{error_msg}"
|
210
|
+
else
|
211
|
+
raise VortexError, "Unexpected response (#{response.status}): #{response.body}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Base64URL encode (no padding, URL-safe)
|
216
|
+
def base64url_encode(data)
|
217
|
+
Base64.urlsafe_encode64(data).tr('=', '')
|
218
|
+
end
|
219
|
+
|
220
|
+
# Format binary UUID data as string (same as Node.js uuidStringify)
|
221
|
+
def format_uuid(bytes)
|
222
|
+
return nil unless bytes.length == 16
|
223
|
+
|
224
|
+
# Convert to hex and format as UUID
|
225
|
+
hex = bytes.unpack1('H*')
|
226
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
data/lib/vortex/error.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vortex
|
4
|
+
# Custom error class for Vortex SDK exceptions
|
5
|
+
#
|
6
|
+
# All Vortex-related errors inherit from this class, making it easy
|
7
|
+
# to catch and handle Vortex-specific exceptions.
|
8
|
+
class VortexError < StandardError
|
9
|
+
# @param message [String] Error message
|
10
|
+
# @param cause [Exception, nil] Original exception that caused this error
|
11
|
+
def initialize(message = nil, cause = nil)
|
12
|
+
super(message)
|
13
|
+
@cause = cause
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Exception, nil] The original exception that caused this error
|
17
|
+
attr_reader :cause
|
18
|
+
end
|
19
|
+
end
|