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.
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vortex/client'
4
+ require 'vortex/error'
5
+
6
+ module Vortex
7
+ module Rails
8
+ # Rails controller integration for Vortex SDK
9
+ #
10
+ # This module provides the same route structure as other SDKs (Express, Java, Python)
11
+ # to ensure complete compatibility with React providers and frontend frameworks.
12
+ #
13
+ # Usage in Rails controller:
14
+ # class VortexController < ApplicationController
15
+ # include Vortex::Rails::Controller
16
+ #
17
+ # private
18
+ #
19
+ # def authenticate_vortex_user
20
+ # # Return user data or nil
21
+ # end
22
+ #
23
+ # def authorize_vortex_operation(operation, user)
24
+ # # Return true/false
25
+ # end
26
+ #
27
+ # def vortex_client
28
+ # @vortex_client ||= Vortex::Client.new(ENV['VORTEX_API_KEY'])
29
+ # end
30
+ # end
31
+ module Controller
32
+ extend ActiveSupport::Concern
33
+
34
+ included do
35
+ # Ensure these routes match exactly with other SDKs for React provider compatibility
36
+ rescue_from Vortex::VortexError, with: :handle_vortex_error
37
+ end
38
+
39
+ # Generate JWT for authenticated user
40
+ # POST /api/vortex/jwt
41
+ def generate_jwt
42
+ user = authenticate_vortex_user
43
+ return render_unauthorized('Authentication required') unless user
44
+
45
+ unless authorize_vortex_operation('JWT', user)
46
+ return render_forbidden('Not authorized to generate JWT')
47
+ end
48
+
49
+ jwt = vortex_client.generate_jwt(
50
+ user_id: user[:user_id],
51
+ identifiers: user[:identifiers],
52
+ groups: user[:groups],
53
+ role: user[:role]
54
+ )
55
+
56
+ render json: { jwt: jwt }
57
+ rescue Vortex::VortexError => e
58
+ render_server_error("Failed to generate JWT: #{e.message}")
59
+ end
60
+
61
+ # Get invitations by target
62
+ # GET /api/vortex/invitations?targetType=email&targetValue=user@example.com
63
+ def get_invitations_by_target
64
+ user = authenticate_vortex_user
65
+ return render_unauthorized('Authentication required') unless user
66
+
67
+ unless authorize_vortex_operation('GET_INVITATIONS', user)
68
+ return render_forbidden('Not authorized to get invitations')
69
+ end
70
+
71
+ target_type = params[:targetType]
72
+ target_value = params[:targetValue]
73
+
74
+ return render_bad_request('Missing targetType or targetValue') unless target_type && target_value
75
+
76
+ invitations = vortex_client.get_invitations_by_target(target_type, target_value)
77
+ render json: { invitations: invitations }
78
+ rescue Vortex::VortexError => e
79
+ render_server_error("Failed to get invitations: #{e.message}")
80
+ end
81
+
82
+ # Get specific invitation by ID
83
+ # GET /api/vortex/invitations/:invitation_id
84
+ def get_invitation
85
+ user = authenticate_vortex_user
86
+ return render_unauthorized('Authentication required') unless user
87
+
88
+ unless authorize_vortex_operation('GET_INVITATION', user)
89
+ return render_forbidden('Not authorized to get invitation')
90
+ end
91
+
92
+ invitation_id = params[:invitation_id]
93
+ invitation = vortex_client.get_invitation(invitation_id)
94
+ render json: invitation
95
+ rescue Vortex::VortexError => e
96
+ render_not_found("Invitation not found: #{e.message}")
97
+ end
98
+
99
+ # Revoke (delete) invitation
100
+ # DELETE /api/vortex/invitations/:invitation_id
101
+ def revoke_invitation
102
+ user = authenticate_vortex_user
103
+ return render_unauthorized('Authentication required') unless user
104
+
105
+ unless authorize_vortex_operation('REVOKE_INVITATION', user)
106
+ return render_forbidden('Not authorized to revoke invitation')
107
+ end
108
+
109
+ invitation_id = params[:invitation_id]
110
+ vortex_client.revoke_invitation(invitation_id)
111
+ render json: { success: true }
112
+ rescue Vortex::VortexError => e
113
+ render_server_error("Failed to revoke invitation: #{e.message}")
114
+ end
115
+
116
+ # Accept invitations
117
+ # POST /api/vortex/invitations/accept
118
+ def accept_invitations
119
+ user = authenticate_vortex_user
120
+ return render_unauthorized('Authentication required') unless user
121
+
122
+ unless authorize_vortex_operation('ACCEPT_INVITATIONS', user)
123
+ return render_forbidden('Not authorized to accept invitations')
124
+ end
125
+
126
+ invitation_ids = params[:invitationIds]
127
+ target = params[:target]
128
+
129
+ unless invitation_ids && target
130
+ return render_bad_request('Missing invitationIds or target')
131
+ end
132
+
133
+ result = vortex_client.accept_invitations(invitation_ids, target)
134
+ render json: result
135
+ rescue Vortex::VortexError => e
136
+ render_server_error("Failed to accept invitations: #{e.message}")
137
+ end
138
+
139
+ # Get invitations by group
140
+ # GET /api/vortex/invitations/by-group/:group_type/:group_id
141
+ def get_invitations_by_group
142
+ user = authenticate_vortex_user
143
+ return render_unauthorized('Authentication required') unless user
144
+
145
+ unless authorize_vortex_operation('GET_GROUP_INVITATIONS', user)
146
+ return render_forbidden('Not authorized to get group invitations')
147
+ end
148
+
149
+ group_type = params[:group_type]
150
+ group_id = params[:group_id]
151
+
152
+ invitations = vortex_client.get_invitations_by_group(group_type, group_id)
153
+ render json: { invitations: invitations }
154
+ rescue Vortex::VortexError => e
155
+ render_server_error("Failed to get group invitations: #{e.message}")
156
+ end
157
+
158
+ # Delete invitations by group
159
+ # DELETE /api/vortex/invitations/by-group/:group_type/:group_id
160
+ def delete_invitations_by_group
161
+ user = authenticate_vortex_user
162
+ return render_unauthorized('Authentication required') unless user
163
+
164
+ unless authorize_vortex_operation('DELETE_GROUP_INVITATIONS', user)
165
+ return render_forbidden('Not authorized to delete group invitations')
166
+ end
167
+
168
+ group_type = params[:group_type]
169
+ group_id = params[:group_id]
170
+
171
+ vortex_client.delete_invitations_by_group(group_type, group_id)
172
+ render json: { success: true }
173
+ rescue Vortex::VortexError => e
174
+ render_server_error("Failed to delete group invitations: #{e.message}")
175
+ end
176
+
177
+ # Reinvite user
178
+ # POST /api/vortex/invitations/:invitation_id/reinvite
179
+ def reinvite
180
+ user = authenticate_vortex_user
181
+ return render_unauthorized('Authentication required') unless user
182
+
183
+ unless authorize_vortex_operation('REINVITE', user)
184
+ return render_forbidden('Not authorized to reinvite')
185
+ end
186
+
187
+ invitation_id = params[:invitation_id]
188
+ result = vortex_client.reinvite(invitation_id)
189
+ render json: result
190
+ rescue Vortex::VortexError => e
191
+ render_server_error("Failed to reinvite: #{e.message}")
192
+ end
193
+
194
+ private
195
+
196
+ # These methods should be implemented in the including controller
197
+ def authenticate_vortex_user
198
+ raise NotImplementedError, 'authenticate_vortex_user must be implemented'
199
+ end
200
+
201
+ def authorize_vortex_operation(operation, user)
202
+ raise NotImplementedError, 'authorize_vortex_operation must be implemented'
203
+ end
204
+
205
+ def vortex_client
206
+ raise NotImplementedError, 'vortex_client must be implemented'
207
+ end
208
+
209
+ # Error response helpers
210
+ def render_unauthorized(message)
211
+ render json: { error: message }, status: :unauthorized
212
+ end
213
+
214
+ def render_forbidden(message)
215
+ render json: { error: message }, status: :forbidden
216
+ end
217
+
218
+ def render_bad_request(message)
219
+ render json: { error: message }, status: :bad_request
220
+ end
221
+
222
+ def render_not_found(message)
223
+ render json: { error: message }, status: :not_found
224
+ end
225
+
226
+ def render_server_error(message)
227
+ render json: { error: message }, status: :internal_server_error
228
+ end
229
+
230
+ def handle_vortex_error(error)
231
+ Rails.logger.error("Vortex error: #{error.message}")
232
+ render_server_error(error.message)
233
+ end
234
+ end
235
+
236
+ # Rails routes helper
237
+ #
238
+ # Usage in routes.rb:
239
+ # Rails.application.routes.draw do
240
+ # mount Vortex::Rails.routes => '/api/vortex'
241
+ # end
242
+ def self.routes
243
+ proc do
244
+ scope '/api/vortex', controller: 'vortex' do
245
+ post 'jwt', action: 'generate_jwt'
246
+ get 'invitations', action: 'get_invitations_by_target'
247
+ get 'invitations/:invitation_id', action: 'get_invitation'
248
+ delete 'invitations/:invitation_id', action: 'revoke_invitation'
249
+ post 'invitations/accept', action: 'accept_invitations'
250
+ get 'invitations/by-group/:group_type/:group_id', action: 'get_invitations_by_group'
251
+ delete 'invitations/by-group/:group_type/:group_id', action: 'delete_invitations_by_group'
252
+ post 'invitations/:invitation_id/reinvite', action: 'reinvite'
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'vortex/client'
5
+ require 'vortex/error'
6
+
7
+ module Vortex
8
+ module Sinatra
9
+ # Sinatra application integration for Vortex SDK
10
+ #
11
+ # This module provides the same route structure as other SDKs (Express, Java, Python)
12
+ # to ensure complete compatibility with React providers and frontend frameworks.
13
+ #
14
+ # Usage in Sinatra app:
15
+ # require 'sinatra/base'
16
+ # require 'vortex/sinatra'
17
+ #
18
+ # class MyApp < Sinatra::Base
19
+ # register Vortex::Sinatra
20
+ #
21
+ # configure do
22
+ # set :vortex_api_key, ENV['VORTEX_API_KEY']
23
+ # set :vortex_base_url, ENV['VORTEX_BASE_URL'] # optional
24
+ # end
25
+ #
26
+ # # Implement authentication callbacks
27
+ # def authenticate_vortex_user
28
+ # # Return user hash or nil
29
+ # end
30
+ #
31
+ # def authorize_vortex_operation(operation, user)
32
+ # # Return true/false
33
+ # end
34
+ # end
35
+ def self.registered(app)
36
+ app.helpers Helpers
37
+
38
+ # Ensure these routes match exactly with other SDKs for React provider compatibility
39
+
40
+ # Generate JWT for authenticated user
41
+ # POST /api/vortex/jwt
42
+ app.post '/api/vortex/jwt' do
43
+ with_vortex_error_handling do
44
+ user = authenticate_vortex_user
45
+ return render_unauthorized('Authentication required') unless user
46
+
47
+ unless authorize_vortex_operation('JWT', user)
48
+ return render_forbidden('Not authorized to generate JWT')
49
+ end
50
+
51
+ jwt = vortex_client.generate_jwt(
52
+ user_id: user[:user_id],
53
+ identifiers: user[:identifiers],
54
+ groups: user[:groups],
55
+ role: user[:role]
56
+ )
57
+
58
+ render_json({ jwt: jwt })
59
+ end
60
+ end
61
+
62
+ # Get invitations by target
63
+ # GET /api/vortex/invitations?targetType=email&targetValue=user@example.com
64
+ app.get '/api/vortex/invitations' do
65
+ with_vortex_error_handling do
66
+ user = authenticate_vortex_user
67
+ return render_unauthorized('Authentication required') unless user
68
+
69
+ unless authorize_vortex_operation('GET_INVITATIONS', user)
70
+ return render_forbidden('Not authorized to get invitations')
71
+ end
72
+
73
+ target_type = params['targetType']
74
+ target_value = params['targetValue']
75
+
76
+ unless target_type && target_value
77
+ return render_bad_request('Missing targetType or targetValue')
78
+ end
79
+
80
+ invitations = vortex_client.get_invitations_by_target(target_type, target_value)
81
+ render_json({ invitations: invitations })
82
+ end
83
+ end
84
+
85
+ # Get specific invitation by ID
86
+ # GET /api/vortex/invitations/:invitation_id
87
+ app.get '/api/vortex/invitations/:invitation_id' do
88
+ with_vortex_error_handling do
89
+ user = authenticate_vortex_user
90
+ return render_unauthorized('Authentication required') unless user
91
+
92
+ unless authorize_vortex_operation('GET_INVITATION', user)
93
+ return render_forbidden('Not authorized to get invitation')
94
+ end
95
+
96
+ invitation_id = params['invitation_id']
97
+ invitation = vortex_client.get_invitation(invitation_id)
98
+ render_json(invitation)
99
+ end
100
+ end
101
+
102
+ # Revoke (delete) invitation
103
+ # DELETE /api/vortex/invitations/:invitation_id
104
+ app.delete '/api/vortex/invitations/:invitation_id' do
105
+ with_vortex_error_handling do
106
+ user = authenticate_vortex_user
107
+ return render_unauthorized('Authentication required') unless user
108
+
109
+ unless authorize_vortex_operation('REVOKE_INVITATION', user)
110
+ return render_forbidden('Not authorized to revoke invitation')
111
+ end
112
+
113
+ invitation_id = params['invitation_id']
114
+ vortex_client.revoke_invitation(invitation_id)
115
+ render_json({ success: true })
116
+ end
117
+ end
118
+
119
+ # Accept invitations
120
+ # POST /api/vortex/invitations/accept
121
+ app.post '/api/vortex/invitations/accept' do
122
+ with_vortex_error_handling do
123
+ user = authenticate_vortex_user
124
+ return render_unauthorized('Authentication required') unless user
125
+
126
+ unless authorize_vortex_operation('ACCEPT_INVITATIONS', user)
127
+ return render_forbidden('Not authorized to accept invitations')
128
+ end
129
+
130
+ request_body = parse_json_body
131
+
132
+ invitation_ids = request_body['invitationIds']
133
+ target = request_body['target']
134
+
135
+ unless invitation_ids && target
136
+ return render_bad_request('Missing invitationIds or target')
137
+ end
138
+
139
+ result = vortex_client.accept_invitations(invitation_ids, target)
140
+ render_json(result)
141
+ end
142
+ end
143
+
144
+ # Get invitations by group
145
+ # GET /api/vortex/invitations/by-group/:group_type/:group_id
146
+ app.get '/api/vortex/invitations/by-group/:group_type/:group_id' do
147
+ with_vortex_error_handling do
148
+ user = authenticate_vortex_user
149
+ return render_unauthorized('Authentication required') unless user
150
+
151
+ unless authorize_vortex_operation('GET_GROUP_INVITATIONS', user)
152
+ return render_forbidden('Not authorized to get group invitations')
153
+ end
154
+
155
+ group_type = params['group_type']
156
+ group_id = params['group_id']
157
+
158
+ invitations = vortex_client.get_invitations_by_group(group_type, group_id)
159
+ render_json({ invitations: invitations })
160
+ end
161
+ end
162
+
163
+ # Delete invitations by group
164
+ # DELETE /api/vortex/invitations/by-group/:group_type/:group_id
165
+ app.delete '/api/vortex/invitations/by-group/:group_type/:group_id' do
166
+ with_vortex_error_handling do
167
+ user = authenticate_vortex_user
168
+ return render_unauthorized('Authentication required') unless user
169
+
170
+ unless authorize_vortex_operation('DELETE_GROUP_INVITATIONS', user)
171
+ return render_forbidden('Not authorized to delete group invitations')
172
+ end
173
+
174
+ group_type = params['group_type']
175
+ group_id = params['group_id']
176
+
177
+ vortex_client.delete_invitations_by_group(group_type, group_id)
178
+ render_json({ success: true })
179
+ end
180
+ end
181
+
182
+ # Reinvite user
183
+ # POST /api/vortex/invitations/:invitation_id/reinvite
184
+ app.post '/api/vortex/invitations/:invitation_id/reinvite' do
185
+ with_vortex_error_handling do
186
+ user = authenticate_vortex_user
187
+ return render_unauthorized('Authentication required') unless user
188
+
189
+ unless authorize_vortex_operation('REINVITE', user)
190
+ return render_forbidden('Not authorized to reinvite')
191
+ end
192
+
193
+ invitation_id = params['invitation_id']
194
+ result = vortex_client.reinvite(invitation_id)
195
+ render_json(result)
196
+ end
197
+ end
198
+
199
+ # Configure Vortex client
200
+ app.configure do
201
+ unless app.respond_to?(:vortex_client)
202
+ app.set :vortex_client, nil
203
+ end
204
+ end
205
+ end
206
+
207
+ # Helper methods for Sinatra apps using Vortex
208
+ module Helpers
209
+ def vortex_client
210
+ @vortex_client ||= begin
211
+ api_key = settings.vortex_api_key
212
+ base_url = settings.respond_to?(:vortex_base_url) ? settings.vortex_base_url : nil
213
+
214
+ raise 'Vortex API key not configured' unless api_key
215
+
216
+ Vortex::Client.new(api_key, base_url: base_url)
217
+ end
218
+ end
219
+
220
+ def authenticate_vortex_user
221
+ # Default implementation - should be overridden in app
222
+ nil
223
+ end
224
+
225
+ def authorize_vortex_operation(operation, user)
226
+ # Default implementation - should be overridden in app
227
+ user != nil
228
+ end
229
+
230
+ def with_vortex_error_handling(&block)
231
+ yield
232
+ rescue Vortex::VortexError => e
233
+ logger.error("Vortex error: #{e.message}") if respond_to?(:logger)
234
+ render_server_error("Vortex error: #{e.message}")
235
+ rescue => e
236
+ logger.error("Unexpected error: #{e.message}") if respond_to?(:logger)
237
+ render_server_error("Internal server error")
238
+ end
239
+
240
+ def parse_json_body
241
+ request.body.rewind
242
+ body = request.body.read
243
+ return {} if body.empty?
244
+
245
+ JSON.parse(body)
246
+ rescue JSON::ParserError
247
+ halt 400, render_json({ error: 'Invalid JSON in request body' })
248
+ end
249
+
250
+ def render_json(data)
251
+ content_type :json
252
+ JSON.generate(data)
253
+ end
254
+
255
+ def render_unauthorized(message)
256
+ halt 401, render_json({ error: message })
257
+ end
258
+
259
+ def render_forbidden(message)
260
+ halt 403, render_json({ error: message })
261
+ end
262
+
263
+ def render_bad_request(message)
264
+ halt 400, render_json({ error: message })
265
+ end
266
+
267
+ def render_not_found(message)
268
+ halt 404, render_json({ error: message })
269
+ end
270
+
271
+ def render_server_error(message)
272
+ halt 500, render_json({ error: message })
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vortex
4
+ VERSION = '1.0.0'
5
+ end
data/lib/vortex.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vortex/version'
4
+ require 'vortex/error'
5
+ require 'vortex/client'
6
+
7
+ # Vortex Ruby SDK
8
+ #
9
+ # This gem provides a Ruby interface to the Vortex invitation system,
10
+ # with the same functionality and API compatibility as other Vortex SDKs
11
+ # (Node.js, Python, Java, Go).
12
+ #
13
+ # Features:
14
+ # - JWT generation with identical algorithm to other SDKs
15
+ # - Complete invitation management API
16
+ # - Rails and Sinatra framework integrations
17
+ # - Same route structure for React provider compatibility
18
+ #
19
+ # Basic usage:
20
+ # require 'vortex'
21
+ #
22
+ # client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
23
+ #
24
+ # jwt = client.generate_jwt(
25
+ # user_id: 'user123',
26
+ # identifiers: [{ type: 'email', value: 'user@example.com' }],
27
+ # groups: [{ id: 'team1', type: 'team', name: 'Engineering' }],
28
+ # role: 'admin'
29
+ # )
30
+ #
31
+ # Framework integrations:
32
+ # # Rails
33
+ # require 'vortex/rails'
34
+ #
35
+ # # Sinatra
36
+ # require 'vortex/sinatra'
37
+ module Vortex
38
+ class << self
39
+ # Create a new Vortex client instance
40
+ #
41
+ # @param api_key [String] Your Vortex API key
42
+ # @param base_url [String] Custom base URL (optional)
43
+ # @return [Vortex::Client] A new client instance
44
+ def new(api_key, base_url: nil)
45
+ Client.new(api_key, base_url: base_url)
46
+ end
47
+
48
+ # Get the current version of the SDK
49
+ #
50
+ # @return [String] Version string
51
+ def version
52
+ VERSION
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/vortex/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'vortex-ruby-sdk'
7
+ spec.version = Vortex::VERSION
8
+ spec.authors = ['Vortex Software']
9
+ spec.email = ['support@vortexsoftware.io']
10
+
11
+ spec.summary = 'Ruby SDK for Vortex invitation system'
12
+ spec.description = 'A Ruby SDK that provides seamless integration with the Vortex invitation system, including JWT generation and invitation management.'
13
+ spec.homepage = 'https://github.com/vortexsoftware/vortex-ruby-sdk'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.0.0'
16
+
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = spec.homepage
20
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) ||
26
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
27
+ end
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ # Runtime dependencies
34
+ spec.add_dependency 'faraday', '~> 2.0'
35
+ spec.add_dependency 'faraday-net_http', '~> 3.0'
36
+
37
+ # Framework integration dependencies (optional)
38
+ spec.add_dependency 'rack', '>= 2.0'
39
+
40
+ # Development dependencies
41
+ spec.add_development_dependency 'rspec', '~> 3.0'
42
+ spec.add_development_dependency 'webmock', '~> 3.0'
43
+ spec.add_development_dependency 'rubocop', '~> 1.0'
44
+ spec.add_development_dependency 'yard', '~> 0.9'
45
+ spec.add_development_dependency 'bundler', '>= 2.0'
46
+ spec.add_development_dependency 'rake', '~> 13.0'
47
+ end